Compare commits
6 Commits
8992060cb7
...
5ace474d71
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ace474d71 | ||
|
|
8de1351349 | ||
|
|
d8c6aa8d8e | ||
|
|
6a476fab1a | ||
|
|
0934b4f3af | ||
|
|
09fd339e89 |
@ -583,3 +583,23 @@ model Setting {
|
|||||||
@@index([category])
|
@@index([category])
|
||||||
@@map("settings")
|
@@map("settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model SmsLog {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
receptor String @db.VarChar(20)
|
||||||
|
type String @db.VarChar(50) // OTP, ORDER_CONFIRMATION, SHIPPING_TRACKING, B2B_NOTIFICATION, PET_CARE_REMINDER, TEST, GENERIC
|
||||||
|
patternId Int? @map("pattern_id")
|
||||||
|
args String[] @default([])
|
||||||
|
messageText String? @map("message_text") @db.Text
|
||||||
|
status String @db.VarChar(20) // SUCCESS, FAILED, DISABLED
|
||||||
|
recId String? @map("rec_id") @db.VarChar(50)
|
||||||
|
errorMessage String? @map("error_message") @db.Text
|
||||||
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz()
|
||||||
|
|
||||||
|
@@index([receptor])
|
||||||
|
@@index([type])
|
||||||
|
@@index([status])
|
||||||
|
@@index([createdAt])
|
||||||
|
@@map("sms_logs")
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,8 @@ import { PrescriptionsModule } from './prescriptions/prescriptions.module';
|
|||||||
import { B2BModule } from './b2b/b2b.module';
|
import { B2BModule } from './b2b/b2b.module';
|
||||||
import { PaymentModule } from './payment/payment.module';
|
import { PaymentModule } from './payment/payment.module';
|
||||||
|
|
||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
SmsModule,
|
SmsModule,
|
||||||
@ -77,15 +79,18 @@ export class AppModule implements NestModule {
|
|||||||
|
|
||||||
configure(consumer: MiddlewareConsumer) {
|
configure(consumer: MiddlewareConsumer) {
|
||||||
consumer
|
consumer
|
||||||
.apply((req: any, res: any, next: () => void) => {
|
.apply((req: Request, res: Response, next: NextFunction) => {
|
||||||
MetricsController.incrementRequestCount();
|
MetricsController.incrementRequestCount();
|
||||||
|
|
||||||
const url: string = req.originalUrl || req.url || '';
|
const url: string = req.originalUrl || req.url || '';
|
||||||
const method: string = req.method || '';
|
const method: string = req.method || '';
|
||||||
|
|
||||||
// Exclude options, admin panel requests, static assets, and health metrics from visit counter
|
// Exclude options, admin panel requests, static assets, and health metrics from visit counter
|
||||||
const isAdminPath = url.includes('/admin') || url.includes('/api/admin');
|
const isAdminPath =
|
||||||
const isStaticAsset = url.match(/\.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?|map)$/i);
|
url.includes('/admin') || url.includes('/api/admin');
|
||||||
|
const isStaticAsset = url.match(
|
||||||
|
/\.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?|map)$/i,
|
||||||
|
);
|
||||||
const isIgnoredMethod = method === 'OPTIONS' || method === 'HEAD';
|
const isIgnoredMethod = method === 'OPTIONS' || method === 'HEAD';
|
||||||
|
|
||||||
if (!isAdminPath && !isStaticAsset && !isIgnoredMethod) {
|
if (!isAdminPath && !isStaticAsset && !isIgnoredMethod) {
|
||||||
|
|||||||
@ -49,7 +49,8 @@ export class AuthService {
|
|||||||
// Remove OTP from Redis if SMS failed to avoid phantom codes
|
// Remove OTP from Redis if SMS failed to avoid phantom codes
|
||||||
await this.redisService.del(`otp:${phoneNumber}`);
|
await this.redisService.del(`otp:${phoneNumber}`);
|
||||||
throw new BadRequestException({
|
throw new BadRequestException({
|
||||||
message: 'ارسال پیامک با خطا مواجه شد. لطفاً چند لحظه دیگر دوباره تلاش کنید.',
|
message:
|
||||||
|
'ارسال پیامک با خطا مواجه شد. لطفاً چند لحظه دیگر دوباره تلاش کنید.',
|
||||||
error: 'SMS_DELIVERY_FAILED',
|
error: 'SMS_DELIVERY_FAILED',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
69
backend/src/payment/dto/admin-transaction-filter.dto.ts
Normal file
69
backend/src/payment/dto/admin-transaction-filter.dto.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { IsOptional, IsString, IsNumber, IsIn } from 'class-validator';
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
|
|
||||||
|
export class AdminTransactionFilterDto {
|
||||||
|
@ApiPropertyOptional({ description: 'شماره صفحه', default: 1 })
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsNumber()
|
||||||
|
page?: number = 1;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'تعداد در هر صفحه', default: 15 })
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsNumber()
|
||||||
|
limit?: number = 15;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'جستجو (نام، موبایل، کد پیگیری، شماره مرجع، کد رهگیری)' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
search?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'فیلتر وضعیت (VERIFIED, PENDING, FAILED)' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
status?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'فیلتر درگاه (zibal, wallet, card)' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
gateway?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'فیلتر نوع تراکنش (ORDER, WALLET_TOPUP)' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
type?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'از تاریخ (ISO format)' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
startDate?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'تا تاریخ (ISO format)' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
endDate?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'حداقل مبلغ (تومان)' })
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsNumber()
|
||||||
|
minAmount?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'حداکثر مبلغ (تومان)' })
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsNumber()
|
||||||
|
maxAmount?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'فیلد مرتبسازی', default: 'createdAt' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
sortBy?: string = 'createdAt';
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'جهت مرتبسازی (asc, desc)', default: 'desc' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsIn(['asc', 'desc'])
|
||||||
|
sortOrder?: 'asc' | 'desc' = 'desc';
|
||||||
|
}
|
||||||
@ -18,17 +18,19 @@ import {
|
|||||||
InitiateWalletTopupDto,
|
InitiateWalletTopupDto,
|
||||||
} from './dto/initiate-payment.dto';
|
} from './dto/initiate-payment.dto';
|
||||||
import { ZibalCallbackQueryDto } from './dto/verify-payment.dto';
|
import { ZibalCallbackQueryDto } from './dto/verify-payment.dto';
|
||||||
|
import { AdminTransactionFilterDto } from './dto/admin-transaction-filter.dto';
|
||||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||||
|
import { RolesGuard } from '../common/guards/roles.guard';
|
||||||
|
import { Roles } from '../common/decorators/roles.decorator';
|
||||||
import {
|
import {
|
||||||
ApiTags,
|
ApiTags,
|
||||||
ApiOperation,
|
ApiOperation,
|
||||||
ApiBearerAuth,
|
ApiBearerAuth,
|
||||||
ApiResponse,
|
|
||||||
ApiOkResponse,
|
ApiOkResponse,
|
||||||
ApiBadRequestResponse,
|
ApiBadRequestResponse,
|
||||||
} from '@nestjs/swagger';
|
} from '@nestjs/swagger';
|
||||||
|
|
||||||
@ApiTags('Payment - درگاه پرداخت اینترنتی زیبال (Zibal IPG)')
|
@ApiTags('Payment - درگاه پرداخت اینترنتی زیبال و مدیریت تراکنشها')
|
||||||
@Controller('payment')
|
@Controller('payment')
|
||||||
export class PaymentController {
|
export class PaymentController {
|
||||||
constructor(
|
constructor(
|
||||||
@ -52,7 +54,9 @@ export class PaymentController {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ApiBadRequestResponse({ description: 'خطا در ایجاد تراکنش یا نامعتبر بودن سفارش' })
|
@ApiBadRequestResponse({
|
||||||
|
description: 'خطا در ایجاد تراکنش یا نامعتبر بودن سفارش',
|
||||||
|
})
|
||||||
async initiateOrderPayment(
|
async initiateOrderPayment(
|
||||||
@Req() req: { user: { id: string } },
|
@Req() req: { user: { id: string } },
|
||||||
@Body() body: InitiatePaymentDto,
|
@Body() body: InitiatePaymentDto,
|
||||||
@ -70,14 +74,6 @@ export class PaymentController {
|
|||||||
@ApiOperation({ summary: 'شارژ آنلاین کیف پول از طریق درگاه زیبال' })
|
@ApiOperation({ summary: 'شارژ آنلاین کیف پول از طریق درگاه زیبال' })
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({
|
||||||
description: 'لینک هدایت به درگاه پرداخت برای شارژ کیف پول',
|
description: 'لینک هدایت به درگاه پرداخت برای شارژ کیف پول',
|
||||||
schema: {
|
|
||||||
example: {
|
|
||||||
success: true,
|
|
||||||
trackId: '15966442233311',
|
|
||||||
paymentUrl: 'https://gateway.zibal.ir/start/15966442233311',
|
|
||||||
amount: 500000,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
async initiateWalletTopup(
|
async initiateWalletTopup(
|
||||||
@Req() req: { user: { id: string } },
|
@Req() req: { user: { id: string } },
|
||||||
@ -91,7 +87,9 @@ export class PaymentController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get('zibal/callback')
|
@Get('zibal/callback')
|
||||||
@ApiOperation({ summary: 'دریافت کالبک بازگشت از درگاه زیبال و تایید تراکنش' })
|
@ApiOperation({
|
||||||
|
summary: 'دریافت کالبک بازگشت از درگاه زیبال و تایید تراکنش',
|
||||||
|
})
|
||||||
async handleZibalCallback(
|
async handleZibalCallback(
|
||||||
@Query() query: ZibalCallbackQueryDto,
|
@Query() query: ZibalCallbackQueryDto,
|
||||||
@Res() res: Response,
|
@Res() res: Response,
|
||||||
@ -130,9 +128,10 @@ export class PaymentController {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return res.redirect(`${frontendUrl}/payment/verify?${params.toString()}`);
|
return res.redirect(`${frontendUrl}/payment/verify?${params.toString()}`);
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
|
const msg = err instanceof Error ? err.message : 'خطا در پردازش پرداخت';
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
`${frontendUrl}/payment/verify?success=0&trackId=${trackId}&message=${encodeURIComponent(err.message || 'خطا در پردازش پرداخت')}`,
|
`${frontendUrl}/payment/verify?success=0&trackId=${trackId}&message=${encodeURIComponent(msg)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,7 +148,9 @@ export class PaymentController {
|
|||||||
const orderId = body.orderId;
|
const orderId = body.orderId;
|
||||||
|
|
||||||
if (!trackId) {
|
if (!trackId) {
|
||||||
return res.status(HttpStatus.BAD_REQUEST).json({ success: false, message: 'trackId is required' });
|
return res
|
||||||
|
.status(HttpStatus.BAD_REQUEST)
|
||||||
|
.json({ success: false, message: 'trackId is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await this.paymentService.verifyAndProcess(
|
const result = await this.paymentService.verifyAndProcess(
|
||||||
@ -167,4 +168,42 @@ export class PaymentController {
|
|||||||
async getTransactionStatus(@Param('trackId') trackId: string) {
|
async getTransactionStatus(@Param('trackId') trackId: string) {
|
||||||
return this.paymentService.getTransaction(trackId);
|
return this.paymentService.getTransaction(trackId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- ADMIN ENDPOINTS FOR TRANSACTION LOGS & DIAGNOSTICS ---
|
||||||
|
|
||||||
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
@Roles('Admin')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Get('admin/transactions')
|
||||||
|
@ApiOperation({ summary: 'مدیریت و مشاهده تمامی لاگها و تراکنشهای پرداخت' })
|
||||||
|
async getAdminTransactions(@Query() query: AdminTransactionFilterDto) {
|
||||||
|
return this.paymentService.getAdminTransactions(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
@Roles('Admin')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Get('admin/stats')
|
||||||
|
@ApiOperation({ summary: 'آمار کلی تراکنشها و حجم مبالغ پرداختی' })
|
||||||
|
async getAdminStats() {
|
||||||
|
return this.paymentService.getAdminTransactionStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
@Roles('Admin')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Post('admin/reconcile')
|
||||||
|
@ApiOperation({ summary: 'اجرای فرایند انطباق و استعلام خودکار تراکنشهای بلاتکلیف' })
|
||||||
|
async reconcilePending() {
|
||||||
|
return this.paymentService.reconcilePendingTransactions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
@Roles('Admin')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Get('admin/live-inquiry/:id')
|
||||||
|
@ApiOperation({ summary: 'استعلام زنده وضعیت تراکنش از سرور زیبال' })
|
||||||
|
async liveInquiry(@Param('id') id: string) {
|
||||||
|
return this.paymentService.adminLiveInquiry(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { PrismaService } from '../prisma/prisma.service';
|
|||||||
import { ZibalService } from './zibal.service';
|
import { ZibalService } from './zibal.service';
|
||||||
import { SmsService } from '../common/services/sms.service';
|
import { SmsService } from '../common/services/sms.service';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
|
import { AdminTransactionFilterDto } from './dto/admin-transaction-filter.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PaymentService {
|
export class PaymentService {
|
||||||
@ -36,21 +37,20 @@ export class PaymentService {
|
|||||||
throw new NotFoundException('سفارش مورد نظر یافت نشد');
|
throw new NotFoundException('سفارش مورد نظر یافت نشد');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (order.status !== 'processing' && order.status !== 'pending_payment') {
|
if (['shipped', 'delivered'].includes(order.status)) {
|
||||||
if (['shipped', 'delivered'].includes(order.status)) {
|
throw new BadRequestException('این سفارش قبلاً تکمیل و ارسال شده است');
|
||||||
throw new BadRequestException('این سفارش قبلاً تکمیل و ارسال شده است');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const amountTomans = Number(order.totalAmount);
|
const amountTomans = Number(order.totalAmount);
|
||||||
const amountRials = Math.round(amountTomans * 10);
|
const amountRials = Math.round(amountTomans * 10);
|
||||||
|
|
||||||
if (amountRials < 1000) {
|
if (amountRials < 1000) {
|
||||||
throw new BadRequestException('مبلغ سفارش کمتر از حداقل مجاز درگاه بانکی (۱,۰۰۰ ریال) است');
|
throw new BadRequestException(
|
||||||
|
'مبلغ سفارش کمتر از حداقل مجاز درگاه بانکی (۱,۰۰۰ ریال) است',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const frontendUrl = await this.zibalService.getFrontendUrl();
|
const frontendUrl = await this.zibalService.getFrontendUrl();
|
||||||
// Default callback points to frontend verify page (or backend proxy)
|
|
||||||
const callbackUrl =
|
const callbackUrl =
|
||||||
customCallbackUrl || `${frontendUrl}/payment/verify?orderId=${order.id}`;
|
customCallbackUrl || `${frontendUrl}/payment/verify?orderId=${order.id}`;
|
||||||
|
|
||||||
@ -111,9 +111,11 @@ export class PaymentService {
|
|||||||
orderId: order.id,
|
orderId: order.id,
|
||||||
amount: amountTomans,
|
amount: amountTomans,
|
||||||
};
|
};
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
this.logger.error(`Error requesting payment from Zibal: ${err.message}`, err.stack);
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
throw new BadRequestException(err.message || 'خطا در اتصال به درگاه پرداخت');
|
const stack = err instanceof Error ? err.stack : undefined;
|
||||||
|
this.logger.error(`Error requesting payment from Zibal: ${msg}`, stack);
|
||||||
|
throw new BadRequestException(msg || 'خطا در اتصال به درگاه پرداخت');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,14 +191,15 @@ export class PaymentService {
|
|||||||
paymentUrl: this.zibalService.getStartUrl(trackId),
|
paymentUrl: this.zibalService.getStartUrl(trackId),
|
||||||
amount: amountTomans,
|
amount: amountTomans,
|
||||||
};
|
};
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
this.logger.error(`Error requesting wallet topup from Zibal: ${err.message}`);
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
throw new BadRequestException(err.message || 'خطا در ارتباط با درگاه بانکی');
|
this.logger.error(`Error requesting wallet topup from Zibal: ${msg}`);
|
||||||
|
throw new BadRequestException(msg || 'خطا در ارتباط با درگاه بانکی');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 3. Verify & Process Callback from Zibal
|
* 3. Verify & Process Callback from Zibal (With Idempotency & Amount Match Check)
|
||||||
*/
|
*/
|
||||||
async verifyAndProcess(
|
async verifyAndProcess(
|
||||||
trackId: string,
|
trackId: string,
|
||||||
@ -212,34 +215,42 @@ export class PaymentService {
|
|||||||
where: { trackId },
|
where: { trackId },
|
||||||
include: {
|
include: {
|
||||||
order: {
|
order: {
|
||||||
include: { user: true, orderItems: { include: { product: true } } },
|
include: {
|
||||||
|
user: true,
|
||||||
|
orderItems: { include: { product: true } },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
user: true,
|
user: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!transaction) {
|
if (!transaction && orderIdParam) {
|
||||||
// If transaction not found by trackId, check if orderId exists
|
transaction = await this.prisma.paymentTransaction.findFirst({
|
||||||
if (orderIdParam) {
|
where: { orderId: orderIdParam },
|
||||||
transaction = await this.prisma.paymentTransaction.findFirst({
|
orderBy: { createdAt: 'desc' },
|
||||||
where: { orderId: orderIdParam },
|
include: {
|
||||||
orderBy: { createdAt: 'desc' },
|
order: {
|
||||||
include: {
|
include: {
|
||||||
order: {
|
user: true,
|
||||||
include: { user: true, orderItems: { include: { product: true } } },
|
orderItems: { include: { product: true } },
|
||||||
},
|
},
|
||||||
user: true,
|
|
||||||
},
|
},
|
||||||
});
|
user: true,
|
||||||
}
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!transaction) {
|
if (!transaction) {
|
||||||
throw new NotFoundException('تراکنش پرداخت در سیستم یافت نشد');
|
throw new NotFoundException('تراکنش پرداخت در سیستم یافت نشد');
|
||||||
}
|
}
|
||||||
|
|
||||||
// If transaction is already verified, return successful details immediately
|
// --- IDEMPOTENCY CHECK ---
|
||||||
|
// If transaction is already marked as VERIFIED, return stored confirmation immediately
|
||||||
|
// without executing any duplicate side-effects (wallet increment, charity, status change, SMS).
|
||||||
if (transaction.status === 'VERIFIED') {
|
if (transaction.status === 'VERIFIED') {
|
||||||
|
this.logger.log(
|
||||||
|
`Idempotency Guard: Transaction ${transaction.id} (trackId=${trackId}) is already VERIFIED. Skipping duplicate execution.`,
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
alreadyVerified: true,
|
alreadyVerified: true,
|
||||||
@ -249,13 +260,16 @@ export class PaymentService {
|
|||||||
cardNumber: transaction.cardNumber,
|
cardNumber: transaction.cardNumber,
|
||||||
amount: Number(transaction.amount),
|
amount: Number(transaction.amount),
|
||||||
orderId: transaction.orderId,
|
orderId: transaction.orderId,
|
||||||
|
type: transaction.type,
|
||||||
message: 'تراکنش قبلاً با موفقیت تایید و پردازش شده است',
|
message: 'تراکنش قبلاً با موفقیت تایید و پردازش شده است',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// If user cancelled or Zibal signaled failure at the gate
|
// If user cancelled or Zibal signaled failure at the gate
|
||||||
if (successParam === '0' || statusParam === '3') {
|
if (successParam === '0' || statusParam === '3') {
|
||||||
const statusMessage = this.zibalService.getStatusMessage(statusParam || 3);
|
const statusMessage = this.zibalService.getStatusMessage(
|
||||||
|
statusParam || 3,
|
||||||
|
);
|
||||||
await this.prisma.paymentTransaction.update({
|
await this.prisma.paymentTransaction.update({
|
||||||
where: { id: transaction.id },
|
where: { id: transaction.id },
|
||||||
data: {
|
data: {
|
||||||
@ -300,15 +314,53 @@ export class PaymentService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// SUCCESSFUL PAYMENT!
|
// --- AMOUNT MATCH VERIFICATION ---
|
||||||
const refNumber = verifyRes.refNumber ? String(verifyRes.refNumber) : undefined;
|
const expectedAmountRials = Number(
|
||||||
const cardNumber = verifyRes.cardNumber || undefined;
|
transaction.amountRials ||
|
||||||
const paidDate = verifyRes.paidAt ? new Date(verifyRes.paidAt) : new Date();
|
Math.round(Number(transaction.amount) * 10),
|
||||||
|
);
|
||||||
|
|
||||||
await this.prisma.$transaction(async (tx) => {
|
if (
|
||||||
// 1. Mark transaction as VERIFIED
|
verifyRes.amount &&
|
||||||
await tx.paymentTransaction.update({
|
Number(verifyRes.amount) > 0 &&
|
||||||
|
Number(verifyRes.amount) !== expectedAmountRials
|
||||||
|
) {
|
||||||
|
this.logger.error(
|
||||||
|
`Security Alert: Amount mismatch on trackId=${trackId}! Zibal reported ${verifyRes.amount} Rials, but expected ${expectedAmountRials} Rials in DB.`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.prisma.paymentTransaction.update({
|
||||||
where: { id: transaction.id },
|
where: { id: transaction.id },
|
||||||
|
data: {
|
||||||
|
status: 'FAILED',
|
||||||
|
resultCode: verifyRes.result,
|
||||||
|
message: `مغایرت مالی: مبلغ تایید شده زیبال (${verifyRes.amount} ریال) با مبلغ سیستم (${expectedAmountRials} ریال) مطابقت ندارد.`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
status: 'FAILED',
|
||||||
|
trackId,
|
||||||
|
message: 'خطای امنیتی: مغایرت در مبلغ پرداخت شده با سفارش.',
|
||||||
|
orderId: transaction.orderId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// SUCCESSFUL PAYMENT!
|
||||||
|
const refNumber = verifyRes.refNumber
|
||||||
|
? String(verifyRes.refNumber)
|
||||||
|
: undefined;
|
||||||
|
const cardNumber = verifyRes.cardNumber || undefined;
|
||||||
|
const paidDate = verifyRes.paidAt
|
||||||
|
? new Date(verifyRes.paidAt)
|
||||||
|
: new Date();
|
||||||
|
|
||||||
|
// Execute atomic status update with concurrency guard
|
||||||
|
await this.prisma.$transaction(async (tx) => {
|
||||||
|
// Optimistic lock: only update if still PENDING (prevents race condition)
|
||||||
|
const updatedCount = await tx.paymentTransaction.updateMany({
|
||||||
|
where: { id: transaction.id, status: 'PENDING' },
|
||||||
data: {
|
data: {
|
||||||
status: 'VERIFIED',
|
status: 'VERIFIED',
|
||||||
resultCode: verifyRes.result,
|
resultCode: verifyRes.result,
|
||||||
@ -319,6 +371,14 @@ export class PaymentService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// If another concurrent thread verified it first, exit safely
|
||||||
|
if (updatedCount.count === 0) {
|
||||||
|
this.logger.warn(
|
||||||
|
`Concurrent execution detected for transaction ${transaction.id}. Handled safely.`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 2. If it's an Order payment
|
// 2. If it's an Order payment
|
||||||
if (transaction.type === 'ORDER' && transaction.orderId) {
|
if (transaction.type === 'ORDER' && transaction.orderId) {
|
||||||
await tx.order.update({
|
await tx.order.update({
|
||||||
@ -330,7 +390,10 @@ export class PaymentService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// If charity donation was made, update user's total charity
|
// If charity donation was made, update user's total charity
|
||||||
if (transaction.order && Number(transaction.order.charityDonation) > 0) {
|
if (
|
||||||
|
transaction.order &&
|
||||||
|
Number(transaction.order.charityDonation) > 0
|
||||||
|
) {
|
||||||
await tx.user.update({
|
await tx.user.update({
|
||||||
where: { id: transaction.userId },
|
where: { id: transaction.userId },
|
||||||
data: {
|
data: {
|
||||||
@ -369,8 +432,11 @@ export class PaymentService {
|
|||||||
// Send SMS notification if order payment
|
// Send SMS notification if order payment
|
||||||
if (transaction.type === 'ORDER' && transaction.order?.user?.mobile) {
|
if (transaction.type === 'ORDER' && transaction.order?.user?.mobile) {
|
||||||
const mobile = transaction.order.user.mobile;
|
const mobile = transaction.order.user.mobile;
|
||||||
const trackingNum = transaction.order.trackingNumber || transaction.order.id;
|
const trackingNum =
|
||||||
const formattedAmount = Number(transaction.amount).toLocaleString('fa-IR');
|
transaction.order.trackingNumber || transaction.order.id;
|
||||||
|
const formattedAmount = Number(transaction.amount).toLocaleString(
|
||||||
|
'fa-IR',
|
||||||
|
);
|
||||||
|
|
||||||
this.smsService
|
this.smsService
|
||||||
.sendOrderConfirmation(mobile, trackingNum, formattedAmount)
|
.sendOrderConfirmation(mobile, trackingNum, formattedAmount)
|
||||||
@ -390,20 +456,92 @@ export class PaymentService {
|
|||||||
type: transaction.type,
|
type: transaction.type,
|
||||||
message: 'پرداخت با موفقیت انجام شد',
|
message: 'پرداخت با موفقیت انجام شد',
|
||||||
};
|
};
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
this.logger.error(`Error during verify: ${err.message}`, err.stack);
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
|
const stack = err instanceof Error ? err.stack : undefined;
|
||||||
|
this.logger.error(`Error during verify: ${msg}`, stack);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
status: 'ERROR',
|
status: 'ERROR',
|
||||||
trackId,
|
trackId,
|
||||||
message: err.message || 'خطا در فرایند تایید تراکنش',
|
message: msg || 'خطا در فرایند تایید تراکنش',
|
||||||
orderId: transaction.orderId,
|
orderId: transaction.orderId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 4. Get Payment Transaction Details
|
* 4. Auto-Reconciliation / Cron Job for Pending Transactions (استعلام تراکنشهای بلاتکلیف)
|
||||||
|
*/
|
||||||
|
async reconcilePendingTransactions() {
|
||||||
|
this.logger.log('Starting pending transactions reconciliation job...');
|
||||||
|
|
||||||
|
// Find transactions that are PENDING and created between 5 minutes and 24 hours ago
|
||||||
|
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
|
||||||
|
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
const pendingTransactions = await this.prisma.paymentTransaction.findMany({
|
||||||
|
where: {
|
||||||
|
status: 'PENDING',
|
||||||
|
trackId: { not: null },
|
||||||
|
createdAt: {
|
||||||
|
gte: oneDayAgo,
|
||||||
|
lte: fiveMinutesAgo,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: { order: true, user: true },
|
||||||
|
take: 50,
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = {
|
||||||
|
total: pendingTransactions.length,
|
||||||
|
verified: 0,
|
||||||
|
failed: 0,
|
||||||
|
remainedPending: 0,
|
||||||
|
errors: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const tx of pendingTransactions) {
|
||||||
|
if (!tx.trackId) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const inquiry = await this.zibalService.inquiryPayment(tx.trackId);
|
||||||
|
this.logger.log(
|
||||||
|
`Inquiry for trackId=${tx.trackId}: status=${inquiry.status}, result=${inquiry.result}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Status 1 (paid & verified) or 2 (paid, unverified)
|
||||||
|
if (inquiry.status === 1 || inquiry.status === 2 || inquiry.result === 100) {
|
||||||
|
await this.verifyAndProcess(tx.trackId, '1', String(inquiry.status), tx.orderId || undefined);
|
||||||
|
results.verified++;
|
||||||
|
} else if (inquiry.status === 3 || inquiry.status === -2 || inquiry.result === 202) {
|
||||||
|
// Cancelled or failed
|
||||||
|
const msg = this.zibalService.getStatusMessage(inquiry.status || 3);
|
||||||
|
await this.prisma.paymentTransaction.update({
|
||||||
|
where: { id: tx.id },
|
||||||
|
data: {
|
||||||
|
status: 'FAILED',
|
||||||
|
resultCode: inquiry.result,
|
||||||
|
message: msg,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
results.failed++;
|
||||||
|
} else {
|
||||||
|
results.remainedPending++;
|
||||||
|
}
|
||||||
|
} catch (err: unknown) {
|
||||||
|
results.errors++;
|
||||||
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
|
this.logger.warn(`Failed inquiry for trackId=${tx.trackId}: ${msg}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.log(`Reconciliation finished: ${JSON.stringify(results)}`);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 5. Get Payment Transaction Details by TrackID
|
||||||
*/
|
*/
|
||||||
async getTransaction(trackId: string) {
|
async getTransaction(trackId: string) {
|
||||||
const transaction = await this.prisma.paymentTransaction.findUnique({
|
const transaction = await this.prisma.paymentTransaction.findUnique({
|
||||||
@ -416,6 +554,7 @@ export class PaymentService {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
user: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -436,6 +575,215 @@ export class PaymentService {
|
|||||||
paidAt: transaction.paidAt,
|
paidAt: transaction.paidAt,
|
||||||
createdAt: transaction.createdAt,
|
createdAt: transaction.createdAt,
|
||||||
order: transaction.order,
|
order: transaction.order,
|
||||||
|
user: transaction.user ? {
|
||||||
|
id: transaction.user.id,
|
||||||
|
firstName: transaction.user.firstName,
|
||||||
|
lastName: transaction.user.lastName,
|
||||||
|
mobile: transaction.user.mobile,
|
||||||
|
} : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 6. Admin: Get List of Transactions with Advanced Search & Filter
|
||||||
|
*/
|
||||||
|
async getAdminTransactions(filters: AdminTransactionFilterDto) {
|
||||||
|
const {
|
||||||
|
page = 1,
|
||||||
|
limit = 15,
|
||||||
|
search,
|
||||||
|
status,
|
||||||
|
gateway,
|
||||||
|
type,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
minAmount,
|
||||||
|
maxAmount,
|
||||||
|
sortBy = 'createdAt',
|
||||||
|
sortOrder = 'desc',
|
||||||
|
} = filters;
|
||||||
|
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
const where: Prisma.PaymentTransactionWhereInput = {};
|
||||||
|
|
||||||
|
if (status) {
|
||||||
|
where.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gateway) {
|
||||||
|
where.gateway = gateway;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type) {
|
||||||
|
where.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startDate || endDate) {
|
||||||
|
where.createdAt = {};
|
||||||
|
if (startDate) where.createdAt.gte = new Date(startDate);
|
||||||
|
if (endDate) where.createdAt.lte = new Date(endDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minAmount !== undefined || maxAmount !== undefined) {
|
||||||
|
where.amount = {};
|
||||||
|
if (minAmount !== undefined) where.amount.gte = new Prisma.Decimal(minAmount);
|
||||||
|
if (maxAmount !== undefined) where.amount.lte = new Prisma.Decimal(maxAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search && search.trim() !== '') {
|
||||||
|
const s = search.trim();
|
||||||
|
where.OR = [
|
||||||
|
{ trackId: { contains: s, mode: 'insensitive' } },
|
||||||
|
{ refNumber: { contains: s, mode: 'insensitive' } },
|
||||||
|
{ cardNumber: { contains: s, mode: 'insensitive' } },
|
||||||
|
{ description: { contains: s, mode: 'insensitive' } },
|
||||||
|
{
|
||||||
|
user: {
|
||||||
|
OR: [
|
||||||
|
{ firstName: { contains: s, mode: 'insensitive' } },
|
||||||
|
{ lastName: { contains: s, mode: 'insensitive' } },
|
||||||
|
{ mobile: { contains: s, mode: 'insensitive' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
order: {
|
||||||
|
trackingNumber: { contains: s, mode: 'insensitive' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const [data, total] = await Promise.all([
|
||||||
|
this.prisma.paymentTransaction.findMany({
|
||||||
|
where,
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
orderBy: { [sortBy]: sortOrder },
|
||||||
|
include: {
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
mobile: true,
|
||||||
|
email: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
trackingNumber: true,
|
||||||
|
totalAmount: true,
|
||||||
|
status: true,
|
||||||
|
paymentMethod: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
this.prisma.paymentTransaction.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
meta: {
|
||||||
|
total,
|
||||||
|
page,
|
||||||
|
lastPage: Math.ceil(total / limit),
|
||||||
|
limit,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 7. Admin: Get Transaction Statistics
|
||||||
|
*/
|
||||||
|
async getAdminTransactionStats() {
|
||||||
|
const [
|
||||||
|
totalCount,
|
||||||
|
verifiedCount,
|
||||||
|
pendingCount,
|
||||||
|
failedCount,
|
||||||
|
verifiedSum,
|
||||||
|
todayVerified,
|
||||||
|
] = await Promise.all([
|
||||||
|
this.prisma.paymentTransaction.count(),
|
||||||
|
this.prisma.paymentTransaction.count({ where: { status: 'VERIFIED' } }),
|
||||||
|
this.prisma.paymentTransaction.count({ where: { status: 'PENDING' } }),
|
||||||
|
this.prisma.paymentTransaction.count({ where: { status: 'FAILED' } }),
|
||||||
|
this.prisma.paymentTransaction.aggregate({
|
||||||
|
_sum: { amount: true },
|
||||||
|
where: { status: 'VERIFIED' },
|
||||||
|
}),
|
||||||
|
this.prisma.paymentTransaction.aggregate({
|
||||||
|
_sum: { amount: true },
|
||||||
|
where: {
|
||||||
|
status: 'VERIFIED',
|
||||||
|
createdAt: {
|
||||||
|
gte: new Date(new Date().setHours(0, 0, 0, 0)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const totalVolume = Number(verifiedSum._sum.amount || 0);
|
||||||
|
const todayVolume = Number(todayVerified._sum.amount || 0);
|
||||||
|
const successRate =
|
||||||
|
totalCount > 0 ? Math.round((verifiedCount / totalCount) * 100) : 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalCount,
|
||||||
|
verifiedCount,
|
||||||
|
pendingCount,
|
||||||
|
failedCount,
|
||||||
|
totalVolume,
|
||||||
|
todayVolume,
|
||||||
|
successRate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 8. Admin: Perform Live Instant Inquiry on Zibal for a Transaction
|
||||||
|
*/
|
||||||
|
async adminLiveInquiry(transactionId: string) {
|
||||||
|
const transaction = await this.prisma.paymentTransaction.findUnique({
|
||||||
|
where: { id: transactionId },
|
||||||
|
include: { order: true, user: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!transaction) {
|
||||||
|
throw new NotFoundException('تراکنش یافت نشد');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!transaction.trackId) {
|
||||||
|
throw new BadRequestException('این تراکنش فاقد TrackId زیبال است');
|
||||||
|
}
|
||||||
|
|
||||||
|
const inquiryRes = await this.zibalService.inquiryPayment(transaction.trackId);
|
||||||
|
const statusMsg = this.zibalService.getStatusMessage(inquiryRes.status);
|
||||||
|
const resultMsg = this.zibalService.getResultMessage(inquiryRes.result);
|
||||||
|
|
||||||
|
// If transaction was PENDING and inquiry confirms payment, sync automatically
|
||||||
|
if (
|
||||||
|
transaction.status === 'PENDING' &&
|
||||||
|
(inquiryRes.status === 1 || inquiryRes.status === 2 || inquiryRes.result === 100)
|
||||||
|
) {
|
||||||
|
await this.verifyAndProcess(
|
||||||
|
transaction.trackId,
|
||||||
|
'1',
|
||||||
|
String(inquiryRes.status),
|
||||||
|
transaction.orderId || undefined,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
transactionId: transaction.id,
|
||||||
|
trackId: transaction.trackId,
|
||||||
|
currentDbStatus: transaction.status,
|
||||||
|
gatewayResponse: inquiryRes,
|
||||||
|
statusMessage: statusMsg,
|
||||||
|
resultMessage: resultMsg,
|
||||||
|
inquiredAt: new Date(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -115,8 +115,12 @@ export class ZibalService {
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
this.logger.error(`Zibal request HTTP error ${response.status}: ${errorText}`);
|
this.logger.error(
|
||||||
throw new Error(`خطا در اتصال به درگاه زیبال: کد وضعیت ${response.status}`);
|
`Zibal request HTTP error ${response.status}: ${errorText}`,
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
`خطا در اتصال به درگاه زیبال: کد وضعیت ${response.status}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = (await response.json()) as ZibalRequestResponse;
|
const data = (await response.json()) as ZibalRequestResponse;
|
||||||
@ -142,7 +146,9 @@ export class ZibalService {
|
|||||||
trackId: String(trackId),
|
trackId: String(trackId),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.logger.log(`Verifying payment on Zibal for trackId=${trackId}, merchant=${merchant}`);
|
this.logger.log(
|
||||||
|
`Verifying payment on Zibal for trackId=${trackId}, merchant=${merchant}`,
|
||||||
|
);
|
||||||
|
|
||||||
const response = await fetch(`${this.baseUrl}/v1/verify`, {
|
const response = await fetch(`${this.baseUrl}/v1/verify`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -154,8 +160,12 @@ export class ZibalService {
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
this.logger.error(`Zibal verify HTTP error ${response.status}: ${errorText}`);
|
this.logger.error(
|
||||||
throw new Error(`خطا در تایید تراکنش درگاه زیبال: کد وضعیت ${response.status}`);
|
`Zibal verify HTTP error ${response.status}: ${errorText}`,
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
`خطا در تایید تراکنش درگاه زیبال: کد وضعیت ${response.status}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = (await response.json()) as ZibalVerifyResponse;
|
const data = (await response.json()) as ZibalVerifyResponse;
|
||||||
@ -166,7 +176,9 @@ export class ZibalService {
|
|||||||
/**
|
/**
|
||||||
* 4. Inquiry Payment (استعلام وضعیت تراکنش)
|
* 4. Inquiry Payment (استعلام وضعیت تراکنش)
|
||||||
*/
|
*/
|
||||||
async inquiryPayment(trackId: string | number): Promise<ZibalInquiryResponse> {
|
async inquiryPayment(
|
||||||
|
trackId: string | number,
|
||||||
|
): Promise<ZibalInquiryResponse> {
|
||||||
const merchant = await this.getMerchant();
|
const merchant = await this.getMerchant();
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
@ -186,8 +198,12 @@ export class ZibalService {
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
this.logger.error(`Zibal inquiry HTTP error ${response.status}: ${errorText}`);
|
this.logger.error(
|
||||||
throw new Error(`خطا در استعلام تراکنش زیبال: کد وضعیت ${response.status}`);
|
`Zibal inquiry HTTP error ${response.status}: ${errorText}`,
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
`خطا در استعلام تراکنش زیبال: کد وضعیت ${response.status}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = (await response.json()) as ZibalInquiryResponse;
|
const data = (await response.json()) as ZibalInquiryResponse;
|
||||||
|
|||||||
@ -1,15 +1,18 @@
|
|||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
|
Post,
|
||||||
Patch,
|
Patch,
|
||||||
Put,
|
Put,
|
||||||
Delete,
|
Delete,
|
||||||
Body,
|
Body,
|
||||||
Param,
|
Param,
|
||||||
|
Query,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
import { SettingsService, ScientificTermData } from './settings.service';
|
import { SettingsService, ScientificTermData } from './settings.service';
|
||||||
|
import { SmsLogQuery } from '../common/services/sms.service';
|
||||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||||
import { RolesGuard } from '../common/guards/roles.guard';
|
import { RolesGuard } from '../common/guards/roles.guard';
|
||||||
import { Roles } from '../common/decorators/roles.decorator';
|
import { Roles } from '../common/decorators/roles.decorator';
|
||||||
@ -20,7 +23,7 @@ import {
|
|||||||
ApiOkResponse,
|
ApiOkResponse,
|
||||||
} from '@nestjs/swagger';
|
} from '@nestjs/swagger';
|
||||||
|
|
||||||
@ApiTags('Settings - تنظیمات متون پویا، تنظیمات سئو، مالی و سیستم')
|
@ApiTags('Settings - تنظیمات متون پویا، تنظیمات سئو، مالی، سیستم و پیامک')
|
||||||
@Controller('settings')
|
@Controller('settings')
|
||||||
export class SettingsController {
|
export class SettingsController {
|
||||||
constructor(private readonly settingsService: SettingsService) {}
|
constructor(private readonly settingsService: SettingsService) {}
|
||||||
@ -125,4 +128,101 @@ export class SettingsController {
|
|||||||
updateSystemSettings(@Body() body: Prisma.InputJsonValue) {
|
updateSystemSettings(@Body() body: Prisma.InputJsonValue) {
|
||||||
return this.settingsService.updateCategorySetting('system', body);
|
return this.settingsService.updateCategorySetting('system', body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SMS Gateway Settings
|
||||||
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
@Roles('Admin')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Get('sms')
|
||||||
|
@ApiOperation({ summary: 'دریافت تنظیمات درگاه پیامک و کدهای پترن' })
|
||||||
|
getSmsSettings() {
|
||||||
|
return this.settingsService.getSmsSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
@Roles('Admin')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Patch('sms')
|
||||||
|
@ApiOperation({ summary: 'بهروزرسانی تنظیمات درگاه پیامک و کدهای پترن' })
|
||||||
|
updateSmsSettings(@Body() body: Prisma.InputJsonValue) {
|
||||||
|
return this.settingsService.updateSmsSettings(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
@Roles('Admin')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Post('sms/test')
|
||||||
|
@ApiOperation({ summary: 'ارسال پیامک آزمایشی پترن به شماره دلخواه' })
|
||||||
|
testSms(
|
||||||
|
@Body('phone') phone: string,
|
||||||
|
@Body('patternId') patternId?: number,
|
||||||
|
@Body('args') args?: string[],
|
||||||
|
) {
|
||||||
|
return this.settingsService.testSms(phone, patternId, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
@Roles('Admin')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Get('sms/patterns')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'دریافت لیست تمامی پترنهای تعریف شده در ملی پیامک',
|
||||||
|
})
|
||||||
|
getPatterns() {
|
||||||
|
return this.settingsService.getPatterns();
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
@Roles('Admin')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Post('sms/patterns')
|
||||||
|
@ApiOperation({ summary: 'درج الگوی جدید در سامانه ملی پیامک' })
|
||||||
|
addPattern(
|
||||||
|
@Body('title') title: string,
|
||||||
|
@Body('body') body: string,
|
||||||
|
@Body('blackListId') blackListId?: number,
|
||||||
|
) {
|
||||||
|
return this.settingsService.addPattern(title, body, blackListId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
@Roles('Admin')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Put('sms/patterns/:bodyId')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'ویرایش الگوی رد شده یا در حال ویرایش در ملی پیامک',
|
||||||
|
})
|
||||||
|
editPattern(@Param('bodyId') bodyId: string, @Body('body') body: string) {
|
||||||
|
return this.settingsService.editPattern(Number(bodyId), body);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SMS Logs & Tracking Endpoints
|
||||||
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
@Roles('Admin')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Get('sms/logs')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'دریافت گزارشات و لاگهای کامل پیامکهای ارسالی با فیلتر و جستجو',
|
||||||
|
})
|
||||||
|
getSmsLogs(@Query() query: SmsLogQuery) {
|
||||||
|
return this.settingsService.getSmsLogs(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
@Roles('Admin')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Delete('sms/logs/:id')
|
||||||
|
@ApiOperation({ summary: 'حذف یک رکورد لاگ پیامک' })
|
||||||
|
deleteSmsLog(@Param('id') id: string) {
|
||||||
|
return this.settingsService.deleteSmsLog(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
@Roles('Admin')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Delete('sms/logs')
|
||||||
|
@ApiOperation({ summary: 'پاکسازی تمامی لاگهای پیامک' })
|
||||||
|
clearAllSmsLogs() {
|
||||||
|
return this.settingsService.clearAllSmsLogs();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
import { SmsService, SmsLogQuery } from '../common/services/sms.service';
|
||||||
|
|
||||||
export class ScientificTermData {
|
export class ScientificTermData {
|
||||||
term?: string;
|
term?: string;
|
||||||
@ -10,7 +11,10 @@ export class ScientificTermData {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SettingsService {
|
export class SettingsService {
|
||||||
constructor(private prisma: PrismaService) {}
|
constructor(
|
||||||
|
private prisma: PrismaService,
|
||||||
|
private smsService: SmsService,
|
||||||
|
) {}
|
||||||
|
|
||||||
async getUiTexts() {
|
async getUiTexts() {
|
||||||
const [uiTexts, settings] = await Promise.all([
|
const [uiTexts, settings] = await Promise.all([
|
||||||
@ -82,4 +86,45 @@ export class SettingsService {
|
|||||||
create: { key, category, value },
|
create: { key, category, value },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getSmsSettings() {
|
||||||
|
return this.smsService.getSmsConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateSmsSettings(value: Prisma.InputJsonValue) {
|
||||||
|
const key = 'sms_config';
|
||||||
|
return this.prisma.setting.upsert({
|
||||||
|
where: { key },
|
||||||
|
update: { category: 'sms', value },
|
||||||
|
create: { key, category: 'sms', value },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async testSms(targetPhone: string, patternId?: number, args?: string[]) {
|
||||||
|
return this.smsService.testSms(targetPhone, patternId, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPatterns() {
|
||||||
|
return this.smsService.getPatterns();
|
||||||
|
}
|
||||||
|
|
||||||
|
async addPattern(title: string, body: string, blackListId?: number) {
|
||||||
|
return this.smsService.addPattern(title, body, blackListId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async editPattern(bodyId: number, body: string) {
|
||||||
|
return this.smsService.editPattern(bodyId, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSmsLogs(query: SmsLogQuery) {
|
||||||
|
return this.smsService.getSmsLogs(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteSmsLog(id: string) {
|
||||||
|
return this.smsService.deleteSmsLog(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearAllSmsLogs() {
|
||||||
|
return this.smsService.clearAllSmsLogs();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { Users, ShoppingCart, Tag, Settings, LogOut, TrendingUp, FolderTree, FileText, BookOpen, Heart, Package, LayoutDashboard, Languages, Image, Video, PhoneCall, Sparkles, MessageSquareQuote, FlaskConical, Building2, Globe, DollarSign, Sliders } from 'lucide-react';
|
import { Users, ShoppingCart, Tag, Settings, LogOut, TrendingUp, FolderTree, FileText, BookOpen, Heart, Package, LayoutDashboard, Languages, Image, Video, PhoneCall, Sparkles, MessageSquareQuote, FlaskConical, Building2, Globe, DollarSign, Sliders, MessageSquare, Receipt, CreditCard } from 'lucide-react';
|
||||||
import api from '../services/api';
|
import api from '../services/api';
|
||||||
import { useAdminAuthStore } from '../store/adminAuthStore';
|
import { useAdminAuthStore } from '../store/adminAuthStore';
|
||||||
|
|
||||||
@ -16,6 +16,7 @@ const menuGroups = [
|
|||||||
title: 'فروشگاه و تخصصی',
|
title: 'فروشگاه و تخصصی',
|
||||||
items: [
|
items: [
|
||||||
{ icon: ShoppingCart, label: 'سفارشات', path: '/orders' },
|
{ icon: ShoppingCart, label: 'سفارشات', path: '/orders' },
|
||||||
|
{ icon: Receipt, label: 'تراکنشها و لاگ پرداخت', path: '/transactions' },
|
||||||
{ icon: Package, label: 'محصولات', path: '/products' },
|
{ icon: Package, label: 'محصولات', path: '/products' },
|
||||||
{ icon: FolderTree, label: 'دستهبندیها', path: '/categories' },
|
{ icon: FolderTree, label: 'دستهبندیها', path: '/categories' },
|
||||||
{ icon: Tag, label: 'کدهای تخفیف', path: '/coupons' },
|
{ icon: Tag, label: 'کدهای تخفیف', path: '/coupons' },
|
||||||
@ -47,6 +48,7 @@ const menuGroups = [
|
|||||||
title: 'سیستم و تنظیمات',
|
title: 'سیستم و تنظیمات',
|
||||||
items: [
|
items: [
|
||||||
{ icon: Settings, label: 'تنظیمات کلی', path: '/settings' },
|
{ icon: Settings, label: 'تنظیمات کلی', path: '/settings' },
|
||||||
|
{ icon: MessageSquare, label: 'تنظیمات پیامک', path: '/settings/sms' },
|
||||||
{ icon: Globe, label: 'تنظیمات سئو', path: '/settings/seo' },
|
{ icon: Globe, label: 'تنظیمات سئو', path: '/settings/seo' },
|
||||||
{ icon: DollarSign, label: 'تنظیمات مالی & ارسال', path: '/settings/financial' },
|
{ icon: DollarSign, label: 'تنظیمات مالی & ارسال', path: '/settings/financial' },
|
||||||
{ icon: Sliders, label: 'تنظیمات سیستمی', path: '/settings/system' },
|
{ icon: Sliders, label: 'تنظیمات سیستمی', path: '/settings/system' },
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Settings as SettingsIcon, Save, Truck, ShieldAlert, Percent, AlertCircle, CreditCard, Share2 } from 'lucide-react';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Settings as SettingsIcon, Save, Truck, ShieldAlert, Percent, AlertCircle, CreditCard, Share2, MessageSquare } from 'lucide-react';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import api from '../services/api';
|
import api from '../services/api';
|
||||||
import Spinner from '../components/ui/Spinner';
|
import Spinner from '../components/ui/Spinner';
|
||||||
@ -106,6 +107,15 @@ export default function Settings() {
|
|||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-500 font-medium mt-1">مدیریت پارامترهای اصلی و سیستمی کانینا</p>
|
<p className="text-gray-500 font-medium mt-1">مدیریت پارامترهای اصلی و سیستمی کانینا</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
|
<Link
|
||||||
|
to="/settings/sms"
|
||||||
|
className="bg-purple-50 text-purple-700 hover:bg-purple-100 border border-purple-200 text-xs font-bold px-4 py-2.5 rounded-xl transition-all flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<MessageSquare className="w-4 h-4 text-purple-600" />
|
||||||
|
تنظیمات درگاه پیامک (MeliPayamak)
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
|
|||||||
1587
frontend/admin-panel/src/pages/SmsSettingsPage.tsx
Normal file
1587
frontend/admin-panel/src/pages/SmsSettingsPage.tsx
Normal file
File diff suppressed because it is too large
Load Diff
681
frontend/admin-panel/src/pages/Transactions.tsx
Normal file
681
frontend/admin-panel/src/pages/Transactions.tsx
Normal file
@ -0,0 +1,681 @@
|
|||||||
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
CreditCard,
|
||||||
|
Search,
|
||||||
|
RefreshCw,
|
||||||
|
Eye,
|
||||||
|
Copy,
|
||||||
|
CheckCircle2,
|
||||||
|
XCircle,
|
||||||
|
Clock,
|
||||||
|
Filter,
|
||||||
|
DollarSign,
|
||||||
|
TrendingUp,
|
||||||
|
AlertTriangle,
|
||||||
|
Receipt,
|
||||||
|
User,
|
||||||
|
ShoppingBag,
|
||||||
|
ArrowUpDown,
|
||||||
|
ExternalLink,
|
||||||
|
ShieldCheck,
|
||||||
|
RotateCcw
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { toast } from 'react-hot-toast';
|
||||||
|
import api from '../services/api';
|
||||||
|
import Spinner from '../components/ui/Spinner';
|
||||||
|
import Pagination from '../components/ui/Pagination';
|
||||||
|
|
||||||
|
interface Transaction {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
orderId?: string | null;
|
||||||
|
amount: number | string;
|
||||||
|
amountRials?: string | number | null;
|
||||||
|
gateway: string;
|
||||||
|
trackId?: string | null;
|
||||||
|
refNumber?: string | null;
|
||||||
|
cardNumber?: string | null;
|
||||||
|
status: string;
|
||||||
|
resultCode?: number | null;
|
||||||
|
message?: string | null;
|
||||||
|
description?: string | null;
|
||||||
|
type: string;
|
||||||
|
paidAt?: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
user?: {
|
||||||
|
id: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
mobile: string;
|
||||||
|
email?: string;
|
||||||
|
} | null;
|
||||||
|
order?: {
|
||||||
|
id: string;
|
||||||
|
trackingNumber?: string;
|
||||||
|
totalAmount: number | string;
|
||||||
|
status: string;
|
||||||
|
paymentMethod?: string;
|
||||||
|
} | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Stats {
|
||||||
|
totalCount: number;
|
||||||
|
verifiedCount: number;
|
||||||
|
pendingCount: number;
|
||||||
|
failedCount: number;
|
||||||
|
totalVolume: number;
|
||||||
|
todayVolume: number;
|
||||||
|
successRate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Transactions() {
|
||||||
|
const [transactions, setTransactions] = useState<Transaction[]>([]);
|
||||||
|
const [stats, setStats] = useState<Stats | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [isReconciling, setIsReconciling] = useState(false);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [totalPages, setTotalPages] = useState(1);
|
||||||
|
const [totalItems, setTotalItems] = useState(0);
|
||||||
|
|
||||||
|
// Filters
|
||||||
|
const [search, setSearch] = useState('');
|
||||||
|
const [statusFilter, setStatusFilter] = useState('');
|
||||||
|
const [gatewayFilter, setGatewayFilter] = useState('');
|
||||||
|
const [typeFilter, setTypeFilter] = useState('');
|
||||||
|
const [sortBy, setSortBy] = useState('createdAt');
|
||||||
|
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
|
||||||
|
|
||||||
|
// Details Modal
|
||||||
|
const [selectedTx, setSelectedTx] = useState<Transaction | null>(null);
|
||||||
|
const [liveInquiryLoading, setLiveInquiryLoading] = useState(false);
|
||||||
|
const [liveInquiryData, setLiveInquiryData] = useState<any>(null);
|
||||||
|
|
||||||
|
const fetchStats = async () => {
|
||||||
|
try {
|
||||||
|
const res = await api.get('/payment/admin/stats');
|
||||||
|
if (res.data) {
|
||||||
|
setStats(res.data);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to fetch stats', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchTransactions = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('page', String(page));
|
||||||
|
params.append('limit', '15');
|
||||||
|
if (search) params.append('search', search);
|
||||||
|
if (statusFilter) params.append('status', statusFilter);
|
||||||
|
if (gatewayFilter) params.append('gateway', gatewayFilter);
|
||||||
|
if (typeFilter) params.append('type', typeFilter);
|
||||||
|
params.append('sortBy', sortBy);
|
||||||
|
params.append('sortOrder', sortOrder);
|
||||||
|
|
||||||
|
const res = await api.get(`/payment/admin/transactions?${params.toString()}`);
|
||||||
|
if (res.data) {
|
||||||
|
setTransactions(res.data.data || []);
|
||||||
|
setTotalPages(res.data.meta?.lastPage || 1);
|
||||||
|
setTotalItems(res.data.meta?.total || 0);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to fetch transactions', e);
|
||||||
|
toast.error('خطا در بارگذاری لیست تراکنشها');
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, [page, search, statusFilter, gatewayFilter, typeFilter, sortBy, sortOrder]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchTransactions();
|
||||||
|
fetchStats();
|
||||||
|
}, [fetchTransactions]);
|
||||||
|
|
||||||
|
const handleCopy = (text: string, title: string) => {
|
||||||
|
navigator.clipboard.writeText(text);
|
||||||
|
toast.success(`${title} کپی شد!`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReconcile = async () => {
|
||||||
|
try {
|
||||||
|
setIsReconciling(true);
|
||||||
|
const res = await api.post('/payment/admin/reconcile');
|
||||||
|
toast.success(
|
||||||
|
`انطباق تراکنشها انجام شد: ${res.data.verified} تایید شده، ${res.data.failed} ناموفق`,
|
||||||
|
);
|
||||||
|
fetchTransactions();
|
||||||
|
fetchStats();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Reconcile error', e);
|
||||||
|
toast.error('خطا در اجرای استعلام خودکار');
|
||||||
|
} finally {
|
||||||
|
setIsReconciling(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLiveInquiry = async (txId: string) => {
|
||||||
|
try {
|
||||||
|
setLiveInquiryLoading(true);
|
||||||
|
const res = await api.get(`/payment/admin/live-inquiry/${txId}`);
|
||||||
|
setLiveInquiryData(res.data);
|
||||||
|
toast.success('استعلام زنده زیبال با موفقیت دریافت شد');
|
||||||
|
fetchTransactions();
|
||||||
|
fetchStats();
|
||||||
|
} catch (e: any) {
|
||||||
|
const msg = e.response?.data?.message || 'خطا در استعلام از درگاه زیبال';
|
||||||
|
toast.error(msg);
|
||||||
|
} finally {
|
||||||
|
setLiveInquiryLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-black text-gray-900 flex items-center gap-2">
|
||||||
|
<Receipt className="w-7 h-7 text-purple-600" />
|
||||||
|
لاگها و مدیریت تراکنشهای مالی
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-500 font-medium mt-1">
|
||||||
|
رهگیری لحظهای پرداختهای آنلاین زیبال، کارت به کارت، کیف پول و عیبیابی تراکنشها
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleReconcile}
|
||||||
|
disabled={isReconciling}
|
||||||
|
className="bg-indigo-50 text-indigo-700 hover:bg-indigo-100 border border-indigo-200 text-xs font-bold px-4 py-2.5 rounded-xl transition-all flex items-center gap-2 shadow-sm disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<RotateCcw className={`w-4 h-4 ${isReconciling ? 'animate-spin' : ''}`} />
|
||||||
|
<span>{isReconciling ? 'در حال استعلام...' : 'انطباق خودکار تراکنشهای معلق'}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Summary Stat Cards */}
|
||||||
|
{stats && (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
|
<div className="bg-white p-5 rounded-2xl border border-gray-200 shadow-sm flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-xs font-bold text-gray-500">حجم کل تراکنشهای موفق</p>
|
||||||
|
<p className="text-xl font-black text-gray-900 mt-1">
|
||||||
|
{Number(stats.totalVolume).toLocaleString('fa-IR')}{' '}
|
||||||
|
<span className="text-xs font-normal text-gray-400">تومان</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-12 h-12 bg-purple-50 text-purple-600 rounded-2xl flex items-center justify-center">
|
||||||
|
<DollarSign className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white p-5 rounded-2xl border border-gray-200 shadow-sm flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-xs font-bold text-gray-500">پرداختهای موفق امروز</p>
|
||||||
|
<p className="text-xl font-black text-emerald-600 mt-1">
|
||||||
|
{Number(stats.todayVolume).toLocaleString('fa-IR')}{' '}
|
||||||
|
<span className="text-xs font-normal text-gray-400">تومان</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-12 h-12 bg-emerald-50 text-emerald-600 rounded-2xl flex items-center justify-center">
|
||||||
|
<TrendingUp className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white p-5 rounded-2xl border border-gray-200 shadow-sm flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-xs font-bold text-gray-500">نرخ موفقیت پرداختها</p>
|
||||||
|
<p className="text-xl font-black text-indigo-600 mt-1">
|
||||||
|
٪{Number(stats.successRate).toLocaleString('fa-IR')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-12 h-12 bg-indigo-50 text-indigo-600 rounded-2xl flex items-center justify-center">
|
||||||
|
<ShieldCheck className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white p-5 rounded-2xl border border-gray-200 shadow-sm flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-xs font-bold text-gray-500">وضعیت تراکنشها</p>
|
||||||
|
<div className="flex items-center gap-3 mt-1 text-xs font-bold">
|
||||||
|
<span className="text-emerald-600">✓ {stats.verifiedCount}</span>
|
||||||
|
<span className="text-amber-500">⏳ {stats.pendingCount}</span>
|
||||||
|
<span className="text-rose-600">✗ {stats.failedCount}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-12 h-12 bg-gray-50 text-gray-600 rounded-2xl flex items-center justify-center">
|
||||||
|
<Receipt className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Filter and Search Bar */}
|
||||||
|
<div className="bg-white p-5 rounded-2xl border border-gray-200 shadow-sm space-y-4">
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-3">
|
||||||
|
{/* Search Input */}
|
||||||
|
<div className="lg:col-span-2 relative">
|
||||||
|
<Search className="w-4 h-4 text-gray-400 absolute right-3.5 top-3.5" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSearch(e.target.value);
|
||||||
|
setPage(1);
|
||||||
|
}}
|
||||||
|
placeholder="جستجو بر اساس نام، موبایل، Track ID، شماره مرجع..."
|
||||||
|
className="w-full pl-3 pr-10 py-2.5 bg-gray-50 border border-gray-200 rounded-xl text-xs font-bold outline-none focus:ring-2 focus:ring-purple-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Status Filter */}
|
||||||
|
<div>
|
||||||
|
<select
|
||||||
|
value={statusFilter}
|
||||||
|
onChange={(e) => {
|
||||||
|
setStatusFilter(e.target.value);
|
||||||
|
setPage(1);
|
||||||
|
}}
|
||||||
|
className="w-full py-2.5 px-3 bg-gray-50 border border-gray-200 rounded-xl text-xs font-bold outline-none focus:ring-2 focus:ring-purple-500"
|
||||||
|
>
|
||||||
|
<option value="">همه وضعیتها</option>
|
||||||
|
<option value="VERIFIED">موفق (VERIFIED)</option>
|
||||||
|
<option value="PENDING">در انتظار (PENDING)</option>
|
||||||
|
<option value="FAILED">ناموفق (FAILED)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Gateway Filter */}
|
||||||
|
<div>
|
||||||
|
<select
|
||||||
|
value={gatewayFilter}
|
||||||
|
onChange={(e) => {
|
||||||
|
setGatewayFilter(e.target.value);
|
||||||
|
setPage(1);
|
||||||
|
}}
|
||||||
|
className="w-full py-2.5 px-3 bg-gray-50 border border-gray-200 rounded-xl text-xs font-bold outline-none focus:ring-2 focus:ring-purple-500"
|
||||||
|
>
|
||||||
|
<option value="">همه درگاهها</option>
|
||||||
|
<option value="zibal">درگاه زیبال (Zibal)</option>
|
||||||
|
<option value="wallet">کیف پول (Wallet)</option>
|
||||||
|
<option value="card">کارت به کارت</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Type Filter */}
|
||||||
|
<div>
|
||||||
|
<select
|
||||||
|
value={typeFilter}
|
||||||
|
onChange={(e) => {
|
||||||
|
setTypeFilter(e.target.value);
|
||||||
|
setPage(1);
|
||||||
|
}}
|
||||||
|
className="w-full py-2.5 px-3 bg-gray-50 border border-gray-200 rounded-xl text-xs font-bold outline-none focus:ring-2 focus:ring-purple-500"
|
||||||
|
>
|
||||||
|
<option value="">همه انواع</option>
|
||||||
|
<option value="ORDER">پرداخت سفارش (ORDER)</option>
|
||||||
|
<option value="WALLET_TOPUP">شارژ کیف پول (TOPUP)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Total records found */}
|
||||||
|
<div className="flex items-center justify-between text-xs text-gray-500 font-bold pt-2 border-t border-gray-100">
|
||||||
|
<span>مجموع {totalItems.toLocaleString('fa-IR')} تراکنش ثبت شده</span>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setSearch('');
|
||||||
|
setStatusFilter('');
|
||||||
|
setGatewayFilter('');
|
||||||
|
setTypeFilter('');
|
||||||
|
setPage(1);
|
||||||
|
}}
|
||||||
|
className="text-purple-600 hover:underline"
|
||||||
|
>
|
||||||
|
پاک کردن فیلترها
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Transactions Table */}
|
||||||
|
<div className="bg-white rounded-2xl border border-gray-200 shadow-sm overflow-hidden">
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="p-12 flex justify-center">
|
||||||
|
<Spinner size="lg" className="text-purple-600" />
|
||||||
|
</div>
|
||||||
|
) : transactions.length === 0 ? (
|
||||||
|
<div className="p-12 text-center text-gray-500 font-bold">
|
||||||
|
هیچ تراکنشی با فیلترهای مشخص شده یافت نشد.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="w-full text-right border-collapse">
|
||||||
|
<thead>
|
||||||
|
<tr className="bg-gray-50/80 border-b border-gray-200 text-[11px] font-black text-gray-500">
|
||||||
|
<th className="p-4">کاربر خریدار</th>
|
||||||
|
<th className="p-4">سفارش / نوع</th>
|
||||||
|
<th className="p-4">مبلغ (تومان)</th>
|
||||||
|
<th className="p-4">درگاه</th>
|
||||||
|
<th className="p-4">شناسههای پرداخت</th>
|
||||||
|
<th className="p-4">وضعیت</th>
|
||||||
|
<th className="p-4">تاریخ و زمان</th>
|
||||||
|
<th className="p-4 text-center">عملیات</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-100 text-xs font-bold">
|
||||||
|
{transactions.map((tx) => {
|
||||||
|
const isVerified = tx.status === 'VERIFIED';
|
||||||
|
const isPending = tx.status === 'PENDING';
|
||||||
|
const isFailed = tx.status === 'FAILED';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={tx.id} className="hover:bg-gray-50/60 transition-colors">
|
||||||
|
{/* User Info */}
|
||||||
|
<td className="p-4">
|
||||||
|
{tx.user ? (
|
||||||
|
<div>
|
||||||
|
<p className="font-black text-gray-900">
|
||||||
|
{tx.user.firstName} {tx.user.lastName}
|
||||||
|
</p>
|
||||||
|
<p className="text-[11px] text-gray-400 font-mono mt-0.5 dir-ltr text-right">
|
||||||
|
{tx.user.mobile}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span className="text-gray-400">کاربر مهمان</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Order / Type */}
|
||||||
|
<td className="p-4">
|
||||||
|
{tx.type === 'WALLET_TOPUP' ? (
|
||||||
|
<span className="bg-blue-50 text-blue-700 px-2.5 py-1 rounded-lg text-[10px] font-black">
|
||||||
|
شارژ کیف پول
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<span className="text-purple-700 font-mono text-[11px] font-black dir-ltr block">
|
||||||
|
{tx.order?.trackingNumber || 'سفارش آنلاین'}
|
||||||
|
</span>
|
||||||
|
<span className="text-[10px] text-gray-400 font-medium">
|
||||||
|
خرید محصول
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Amount */}
|
||||||
|
<td className="p-4 font-black text-gray-900 font-mono text-sm">
|
||||||
|
{Number(tx.amount).toLocaleString('fa-IR')}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Gateway */}
|
||||||
|
<td className="p-4">
|
||||||
|
<span className="bg-indigo-50 text-indigo-700 px-2.5 py-1 rounded-lg text-[10px] font-black">
|
||||||
|
{tx.gateway === 'zibal' ? 'زیبال (Zibal)' : tx.gateway}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Tracking Numbers */}
|
||||||
|
<td className="p-4 font-mono text-[11px]">
|
||||||
|
{tx.trackId && (
|
||||||
|
<div className="flex items-center gap-1 text-gray-800">
|
||||||
|
<span className="text-gray-400 text-[10px]">Track:</span>
|
||||||
|
<span className="dir-ltr">{tx.trackId}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleCopy(tx.trackId!, 'Track ID')}
|
||||||
|
className="text-gray-400 hover:text-purple-600"
|
||||||
|
>
|
||||||
|
<Copy className="w-3 h-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{tx.refNumber && (
|
||||||
|
<div className="flex items-center gap-1 text-emerald-700 mt-0.5">
|
||||||
|
<span className="text-gray-400 text-[10px]">Ref:</span>
|
||||||
|
<span className="dir-ltr">{tx.refNumber}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleCopy(tx.refNumber!, 'شماره مرجع')}
|
||||||
|
className="text-gray-400 hover:text-purple-600"
|
||||||
|
>
|
||||||
|
<Copy className="w-3 h-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Status */}
|
||||||
|
<td className="p-4">
|
||||||
|
{isVerified && (
|
||||||
|
<span className="inline-flex items-center gap-1 bg-emerald-50 text-emerald-700 border border-emerald-200 px-2.5 py-1 rounded-full text-[10px] font-black">
|
||||||
|
<CheckCircle2 className="w-3 h-3" />
|
||||||
|
موفق
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{isPending && (
|
||||||
|
<span className="inline-flex items-center gap-1 bg-amber-50 text-amber-700 border border-amber-200 px-2.5 py-1 rounded-full text-[10px] font-black">
|
||||||
|
<Clock className="w-3 h-3" />
|
||||||
|
در انتظار
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{isFailed && (
|
||||||
|
<span className="inline-flex items-center gap-1 bg-rose-50 text-rose-700 border border-rose-200 px-2.5 py-1 rounded-full text-[10px] font-black">
|
||||||
|
<XCircle className="w-3 h-3" />
|
||||||
|
ناموفق
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Date */}
|
||||||
|
<td className="p-4 text-[11px] text-gray-500">
|
||||||
|
{new Date(tx.createdAt).toLocaleDateString('fa-IR', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
})}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Actions */}
|
||||||
|
<td className="p-4 text-center">
|
||||||
|
<div className="flex items-center justify-center gap-1.5">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedTx(tx);
|
||||||
|
setLiveInquiryData(null);
|
||||||
|
}}
|
||||||
|
className="p-2 bg-gray-100 hover:bg-purple-50 text-gray-600 hover:text-purple-600 rounded-xl transition-all"
|
||||||
|
title="مشاهده جزئیات و عیبیابی"
|
||||||
|
>
|
||||||
|
<Eye className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{tx.gateway === 'zibal' && tx.trackId && (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedTx(tx);
|
||||||
|
handleLiveInquiry(tx.id);
|
||||||
|
}}
|
||||||
|
className="p-2 bg-indigo-50 hover:bg-indigo-100 text-indigo-600 rounded-xl transition-all"
|
||||||
|
title="استعلام زنده از زیبال"
|
||||||
|
>
|
||||||
|
<RotateCcw className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
<div className="p-4 border-t border-gray-100">
|
||||||
|
<Pagination
|
||||||
|
currentPage={page}
|
||||||
|
totalPages={totalPages}
|
||||||
|
onPageChange={(p) => setPage(p)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Diagnostics and Details Modal */}
|
||||||
|
{selectedTx && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm">
|
||||||
|
<div className="bg-white rounded-3xl w-full max-w-2xl max-h-[90vh] overflow-y-auto border border-gray-200 shadow-2xl p-6 sm:p-8 space-y-6">
|
||||||
|
<div className="flex items-center justify-between border-b border-gray-100 pb-4">
|
||||||
|
<h3 className="text-lg font-black text-gray-900 flex items-center gap-2">
|
||||||
|
<Receipt className="w-5 h-5 text-purple-600" />
|
||||||
|
جزئیات تراکنش و ریشهیابی خطا
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedTx(null);
|
||||||
|
setLiveInquiryData(null);
|
||||||
|
}}
|
||||||
|
className="text-gray-400 hover:text-gray-700 text-xl font-bold"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Quick Grid Info */}
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3 text-xs bg-gray-50 p-4 rounded-2xl border border-gray-200">
|
||||||
|
<div>
|
||||||
|
<span className="text-gray-400 block font-medium">وضعیت تراکنش:</span>
|
||||||
|
<span
|
||||||
|
className={`font-black mt-0.5 inline-block ${
|
||||||
|
selectedTx.status === 'VERIFIED'
|
||||||
|
? 'text-emerald-600'
|
||||||
|
: selectedTx.status === 'PENDING'
|
||||||
|
? 'text-amber-500'
|
||||||
|
: 'text-rose-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{selectedTx.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span className="text-gray-400 block font-medium">مبلغ تراکنش:</span>
|
||||||
|
<span className="font-black text-gray-900 mt-0.5 inline-block">
|
||||||
|
{Number(selectedTx.amount).toLocaleString('fa-IR')} تومان
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span className="text-gray-400 block font-medium">درگاه پرداخت:</span>
|
||||||
|
<span className="font-black text-gray-900 mt-0.5 inline-block">
|
||||||
|
{selectedTx.gateway}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span className="text-gray-400 block font-medium">شناسه پیگیری (Track ID):</span>
|
||||||
|
<span className="font-mono font-bold text-gray-900 dir-ltr inline-block">
|
||||||
|
{selectedTx.trackId || 'ثبت نشده'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span className="text-gray-400 block font-medium">شماره مرجع بانکی:</span>
|
||||||
|
<span className="font-mono font-bold text-gray-900 dir-ltr inline-block">
|
||||||
|
{selectedTx.refNumber || 'ثبت نشده'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span className="text-gray-400 block font-medium">شماره کارت ماسک شده:</span>
|
||||||
|
<span className="font-mono font-bold text-gray-900 dir-ltr inline-block">
|
||||||
|
{selectedTx.cardNumber || 'ثبت نشده'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Error Message / Reason */}
|
||||||
|
{selectedTx.message && (
|
||||||
|
<div
|
||||||
|
className={`p-4 rounded-2xl border ${
|
||||||
|
selectedTx.status === 'VERIFIED'
|
||||||
|
? 'bg-emerald-50 border-emerald-200 text-emerald-900'
|
||||||
|
: 'bg-rose-50 border-rose-200 text-rose-900'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 font-black text-xs mb-1">
|
||||||
|
{selectedTx.status === 'VERIFIED' ? (
|
||||||
|
<CheckCircle2 className="w-4 h-4 text-emerald-600" />
|
||||||
|
) : (
|
||||||
|
<AlertTriangle className="w-4 h-4 text-rose-600" />
|
||||||
|
)}
|
||||||
|
<span>پیام و نتیجه گزارش درگاه:</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs font-bold leading-relaxed">{selectedTx.message}</p>
|
||||||
|
{selectedTx.resultCode && (
|
||||||
|
<p className="text-[11px] font-mono mt-1 text-gray-500">
|
||||||
|
کد نتیجه زیبال: {selectedTx.resultCode}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Live Inquiry Section */}
|
||||||
|
<div className="border border-indigo-100 bg-indigo-50/50 p-5 rounded-2xl space-y-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<RotateCcw className="w-4 h-4 text-indigo-600" />
|
||||||
|
<h4 className="font-black text-xs text-indigo-900">
|
||||||
|
استعلام زنده از سرور زیبال (Live Gateway Inquiry)
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => handleLiveInquiry(selectedTx.id)}
|
||||||
|
disabled={liveInquiryLoading}
|
||||||
|
className="bg-indigo-600 hover:bg-indigo-700 text-white text-xs font-bold px-3 py-1.5 rounded-xl transition-all flex items-center gap-1.5 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<RefreshCw
|
||||||
|
className={`w-3.5 h-3.5 ${liveInquiryLoading ? 'animate-spin' : ''}`}
|
||||||
|
/>
|
||||||
|
<span>استعلام زنده لحظهای</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{liveInquiryData && (
|
||||||
|
<div className="bg-white p-4 rounded-xl border border-indigo-200 space-y-2 text-xs font-mono">
|
||||||
|
<div className="flex justify-between text-gray-700 font-bold font-vazir">
|
||||||
|
<span>وضعیت زیبال:</span>
|
||||||
|
<span className="text-indigo-600">{liveInquiryData.statusMessage}</span>
|
||||||
|
</div>
|
||||||
|
<pre className="text-[11px] bg-slate-900 text-emerald-400 p-3 rounded-lg overflow-x-auto dir-ltr">
|
||||||
|
{JSON.stringify(liveInquiryData.gatewayResponse, null, 2)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Close Button */}
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedTx(null);
|
||||||
|
setLiveInquiryData(null);
|
||||||
|
}}
|
||||||
|
className="bg-gray-100 hover:bg-gray-200 text-gray-800 font-bold px-6 py-2.5 rounded-xl text-xs transition-colors"
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -33,6 +33,8 @@ const B2BManager = lazy(() => import('../pages/B2BManager'));
|
|||||||
const SeoSettingsPage = lazy(() => import('../pages/SeoSettingsPage'));
|
const SeoSettingsPage = lazy(() => import('../pages/SeoSettingsPage'));
|
||||||
const FinancialSettingsPage = lazy(() => import('../pages/FinancialSettingsPage'));
|
const FinancialSettingsPage = lazy(() => import('../pages/FinancialSettingsPage'));
|
||||||
const SystemSettingsPage = lazy(() => import('../pages/SystemSettingsPage'));
|
const SystemSettingsPage = lazy(() => import('../pages/SystemSettingsPage'));
|
||||||
|
const SmsSettingsPage = lazy(() => import('../pages/SmsSettingsPage'));
|
||||||
|
const Transactions = lazy(() => import('../pages/Transactions'));
|
||||||
|
|
||||||
export interface AdminRouteConfig {
|
export interface AdminRouteConfig {
|
||||||
path: string;
|
path: string;
|
||||||
@ -56,8 +58,10 @@ export const router = createBrowserRouter([
|
|||||||
{ path: 'users/*', element: <Users /> },
|
{ path: 'users/*', element: <Users /> },
|
||||||
{ path: 'products/*', element: <Products /> },
|
{ path: 'products/*', element: <Products /> },
|
||||||
{ path: 'orders/*', element: <Orders /> },
|
{ path: 'orders/*', element: <Orders /> },
|
||||||
|
{ path: 'transactions/*', element: <Transactions /> },
|
||||||
{ path: 'coupons/*', element: <Coupons /> },
|
{ path: 'coupons/*', element: <Coupons /> },
|
||||||
{ path: 'settings', element: <Settings /> },
|
{ path: 'settings', element: <Settings /> },
|
||||||
|
{ path: 'settings/sms', element: <SmsSettingsPage /> },
|
||||||
{ path: 'settings/seo', element: <SeoSettingsPage /> },
|
{ path: 'settings/seo', element: <SeoSettingsPage /> },
|
||||||
{ path: 'settings/financial', element: <FinancialSettingsPage /> },
|
{ path: 'settings/financial', element: <FinancialSettingsPage /> },
|
||||||
{ path: 'settings/system', element: <SystemSettingsPage /> },
|
{ path: 'settings/system', element: <SystemSettingsPage /> },
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"0": "AdminService",
|
"0": "AdminService",
|
||||||
"1": "Pet Management Controller",
|
"1": "pets/pets.controller.ts",
|
||||||
"2": "SettingsController",
|
"2": "Roles",
|
||||||
"3": "UsersService",
|
"3": "UsersService",
|
||||||
"4": "CMS Content Management",
|
"4": "CmsController",
|
||||||
"5": "OrdersService",
|
"5": "BannersService",
|
||||||
"6": "auth.controller.ts",
|
"6": "auth.controller.ts",
|
||||||
"7": "app.module.ts",
|
"7": "app.module.ts",
|
||||||
"8": "CreateVideoDto",
|
"8": "CreateVideoDto",
|
||||||
@ -12,32 +12,32 @@
|
|||||||
"10": "SmartAdvisor.tsx",
|
"10": "SmartAdvisor.tsx",
|
||||||
"11": "compilerOptions",
|
"11": "compilerOptions",
|
||||||
"12": "toPersian",
|
"12": "toPersian",
|
||||||
"13": "Media and Blog UI Components",
|
"13": "SmsService",
|
||||||
"14": "ProductsService",
|
"14": "ProductsService",
|
||||||
"15": "lib/services/api.ts",
|
"15": "lib/services/api.ts",
|
||||||
"16": "devDependencies",
|
"16": "eslint",
|
||||||
"17": "useSettingsStore",
|
"17": "useSettingsStore",
|
||||||
"18": "AppModule",
|
"18": "AppModule",
|
||||||
"19": "B2B Inquiry Controller",
|
"19": "B2B Inquiry Controller",
|
||||||
"20": "Category Management Controller",
|
"20": "Category Management Controller",
|
||||||
"21": "Banner Management Controller",
|
"21": "auth.service.ts",
|
||||||
"22": "Media Upload Controller",
|
"22": "MediaController",
|
||||||
"23": "Ingredient Management Controller",
|
"23": "Ingredient Management Controller",
|
||||||
"24": "Admin Dashboard Components",
|
"24": "adminRoutes.tsx",
|
||||||
"25": "admin.module.ts",
|
"25": "admin.module.ts",
|
||||||
"26": "Pet DTOs and Controller",
|
"26": "ConfirmModal.tsx",
|
||||||
"27": "Prescription Review Controller",
|
"27": "Prescription Review Controller",
|
||||||
"28": "Smart Advisor Controller",
|
"28": "Smart Advisor Controller",
|
||||||
"29": "Testimonials Management Controller",
|
"29": "Testimonials Management Controller",
|
||||||
"30": "Admin Sidebar and Layout",
|
"30": "src/services/api.ts",
|
||||||
"31": "Admin Settings Managers",
|
"31": "Spinner.tsx",
|
||||||
"32": "useCartStore",
|
"32": "useCartStore",
|
||||||
"33": "ZibalService",
|
"33": "ZibalService",
|
||||||
"34": "main.ts",
|
"34": "main.ts",
|
||||||
"35": "ContactService",
|
"35": "ContactService",
|
||||||
"36": "Backend TypeScript Config",
|
"36": "Backend TypeScript Config",
|
||||||
"37": "App TypeScript Config",
|
"37": "App TypeScript Config",
|
||||||
"38": "CMS and Media Modals",
|
"38": "Coupons.tsx",
|
||||||
"39": "dependencies",
|
"39": "dependencies",
|
||||||
"40": "Node TypeScript Config",
|
"40": "Node TypeScript Config",
|
||||||
"41": "Admin Blog Controller",
|
"41": "Admin Blog Controller",
|
||||||
@ -45,7 +45,7 @@
|
|||||||
"43": "devDependencies",
|
"43": "devDependencies",
|
||||||
"44": "Generic CRUD Controller",
|
"44": "Generic CRUD Controller",
|
||||||
"45": "seo.module.ts",
|
"45": "seo.module.ts",
|
||||||
"46": "Coupon Management UI",
|
"46": "OrdersController",
|
||||||
"47": "Project Build Scripts",
|
"47": "Project Build Scripts",
|
||||||
"48": "Home Data Module",
|
"48": "Home Data Module",
|
||||||
"49": "devDependencies",
|
"49": "devDependencies",
|
||||||
@ -61,19 +61,19 @@
|
|||||||
"59": "PrismaService",
|
"59": "PrismaService",
|
||||||
"60": "Jest Testing Config",
|
"60": "Jest Testing Config",
|
||||||
"61": "PaginationDto",
|
"61": "PaginationDto",
|
||||||
"62": "BlogsController",
|
"62": "SettingsService",
|
||||||
"63": "FE-001",
|
"63": "FE-001",
|
||||||
"64": "pagination.dto.ts",
|
"64": "payment.module.ts",
|
||||||
"65": "rules/graphify.md",
|
"65": "rules/graphify.md",
|
||||||
"66": "App Health Controller",
|
"66": "App Health Controller",
|
||||||
"67": "dependencies",
|
"67": "dependencies",
|
||||||
"68": "Pet Business Logic",
|
"68": "OrdersService",
|
||||||
"69": "Integrity Validation Scripts",
|
"69": "Integrity Validation Scripts",
|
||||||
"70": "Admin Panel Package Config",
|
"70": "Admin Panel Package Config",
|
||||||
"71": "Analytics and Report Charts",
|
"71": "Analytics and Report Charts",
|
||||||
"72": "Task Orchestration Scripts",
|
"72": "Task Orchestration Scripts",
|
||||||
"73": "Backend Package Config",
|
"73": "Backend Package Config",
|
||||||
"74": "Health Log DTOs",
|
"74": "BlogsController",
|
||||||
"75": "React Error Boundary",
|
"75": "React Error Boundary",
|
||||||
"76": "Application Package Config",
|
"76": "Application Package Config",
|
||||||
"77": "WikiController",
|
"77": "WikiController",
|
||||||
@ -92,7 +92,7 @@
|
|||||||
"90": "Blog Listing Page",
|
"90": "Blog Listing Page",
|
||||||
"91": "Blog Post Detail Page",
|
"91": "Blog Post Detail Page",
|
||||||
"92": "@types/node",
|
"92": "@types/node",
|
||||||
"93": "typescript",
|
"93": "devDependencies",
|
||||||
"94": "UI Text Seeding",
|
"94": "UI Text Seeding",
|
||||||
"95": "Wiki Terms Seeding",
|
"95": "Wiki Terms Seeding",
|
||||||
"96": "Blog Management DTOs",
|
"96": "Blog Management DTOs",
|
||||||
@ -102,7 +102,7 @@
|
|||||||
"100": "Network Status Banner",
|
"100": "Network Status Banner",
|
||||||
"101": "Docker Deployment Scripts",
|
"101": "Docker Deployment Scripts",
|
||||||
"102": "DB-001",
|
"102": "DB-001",
|
||||||
"103": "typescript-eslint",
|
"103": "BlogsService",
|
||||||
"104": "Database Migration Scripts",
|
"104": "Database Migration Scripts",
|
||||||
"105": "Scientific Terms Schema",
|
"105": "Scientific Terms Schema",
|
||||||
"106": "Blog Data Seeding",
|
"106": "Blog Data Seeding",
|
||||||
@ -116,10 +116,10 @@
|
|||||||
"114": "Manifest Data Generation",
|
"114": "Manifest Data Generation",
|
||||||
"115": "Honest Manifest Synchronization",
|
"115": "Honest Manifest Synchronization",
|
||||||
"116": "Manifest Entry Synchronization",
|
"116": "Manifest Entry Synchronization",
|
||||||
"117": "Pet Reminder DTOs",
|
"117": "WikiService",
|
||||||
"118": "TS-001",
|
"118": "TS-001",
|
||||||
"119": "TEST-001",
|
"119": "TEST-001",
|
||||||
"120": "@tailwindcss/postcss",
|
"120": "ValidateCouponDto",
|
||||||
"121": "@types/react-dom",
|
"121": "@types/react-dom",
|
||||||
"122": "Admin Panel TSConfig",
|
"122": "Admin Panel TSConfig",
|
||||||
"123": "About Page Component",
|
"123": "About Page Component",
|
||||||
@ -129,19 +129,19 @@
|
|||||||
"127": "DEVOPS-001",
|
"127": "DEVOPS-001",
|
||||||
"128": "DOC-001",
|
"128": "DOC-001",
|
||||||
"129": "What You Must Do When Invoked",
|
"129": "What You Must Do When Invoked",
|
||||||
"130": "Roles",
|
"130": "JwtAuthGuard",
|
||||||
"131": "راهنمای تست سیستم (Software Testing)",
|
"131": "راهنمای تست سیستم (Software Testing)",
|
||||||
"132": "Role & Core Objective",
|
"132": "Role & Core Objective",
|
||||||
"133": "Required Review Group Closures",
|
"133": "Required Review Group Closures",
|
||||||
"134": "Operational Rules & Boundaries",
|
"134": "Operational Rules & Boundaries",
|
||||||
"135": "Operational Rules & Boundaries",
|
"135": "Operational Rules & Boundaries",
|
||||||
"136": "🏢 AI Software Agency — Master Orchestration Protocol v3",
|
"136": "🏢 AI Software Agency — Master Orchestration Protocol v3",
|
||||||
"137": "NestJS CLI Tooling",
|
"137": "@nestjs/cli",
|
||||||
"138": "Operational Rules & Boundaries",
|
"138": "Operational Rules & Boundaries",
|
||||||
"139": "Operational Rules & Boundaries",
|
"139": "Operational Rules & Boundaries",
|
||||||
"140": "Bcrypt Type Definitions",
|
"140": "Bcrypt Type Definitions",
|
||||||
"141": "Passport JWT Type Definitions",
|
"141": "bcryptjs",
|
||||||
"142": "Supertest Type Definitions",
|
"142": "helmet",
|
||||||
"143": "Blog Entity Model",
|
"143": "Blog Entity Model",
|
||||||
"144": "Home Entity Model",
|
"144": "Home Entity Model",
|
||||||
"145": "Wiki Entity Model",
|
"145": "Wiki Entity Model",
|
||||||
@ -237,7 +237,7 @@
|
|||||||
"235": "👁️ UX & Persona Interface Review (08_visual_qa)",
|
"235": "👁️ UX & Persona Interface Review (08_visual_qa)",
|
||||||
"236": "Compiler Diagnostic Dispositions",
|
"236": "Compiler Diagnostic Dispositions",
|
||||||
"237": "@eslint/eslintrc",
|
"237": "@eslint/eslintrc",
|
||||||
"238": "eslint-plugin-prettier",
|
"238": "js-yaml",
|
||||||
"239": "globals",
|
"239": "globals",
|
||||||
"240": "prettier",
|
"240": "prettier",
|
||||||
"241": "prisma",
|
"241": "prisma",
|
||||||
@ -263,5 +263,25 @@
|
|||||||
"261": "shabnam-font-v5.0.1/CHANGELOG.md",
|
"261": "shabnam-font-v5.0.1/CHANGELOG.md",
|
||||||
"262": "application/CLAUDE.md",
|
"262": "application/CLAUDE.md",
|
||||||
"263": "application/public/fonts/sahel-font-v3.4.0/CHANGELOG.md",
|
"263": "application/public/fonts/sahel-font-v3.4.0/CHANGELOG.md",
|
||||||
"264": "tailwindcss"
|
"264": "tailwindcss",
|
||||||
|
"265": "typescript-eslint",
|
||||||
|
"266": "@nestjs/core",
|
||||||
|
"267": "@nestjs/jwt",
|
||||||
|
"268": "@nestjs/swagger",
|
||||||
|
"269": "@nestjs/throttler",
|
||||||
|
"270": "passport-jwt",
|
||||||
|
"271": "@prisma/client",
|
||||||
|
"272": "swagger-ui-express",
|
||||||
|
"273": "AGENTS.md",
|
||||||
|
"274": "eslint-config-prettier",
|
||||||
|
"275": "@eslint/js",
|
||||||
|
"276": "jest",
|
||||||
|
"277": "@nestjs/schematics",
|
||||||
|
"278": "@nestjs/testing",
|
||||||
|
"279": "source-map-support",
|
||||||
|
"280": "ts-jest",
|
||||||
|
"281": "tsconfig-paths",
|
||||||
|
"282": "@types/bcryptjs",
|
||||||
|
"283": "typescript-eslint",
|
||||||
|
"284": "globals"
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -1,79 +1,79 @@
|
|||||||
{
|
{
|
||||||
"0": "AdminService",
|
"0": "AdminService",
|
||||||
"1": "Pet Management Controller",
|
"1": "PetsController",
|
||||||
"2": "Roles",
|
"2": "Roles",
|
||||||
"3": "UsersService",
|
"3": "UsersService",
|
||||||
"4": "CMS Content Management",
|
"4": "CmsController",
|
||||||
"5": "OrdersController",
|
"5": "BannersService",
|
||||||
"6": "auth.controller.ts",
|
"6": "auth.service.ts",
|
||||||
"7": "app.module.ts",
|
"7": "app.module.ts",
|
||||||
"8": "CreateVideoDto",
|
"8": "CreateVideoDto",
|
||||||
"9": "Public Catalog Pages",
|
"9": "ProductService",
|
||||||
"10": "Client Layout and Auth Modals",
|
"10": "SmartAdvisor.tsx",
|
||||||
"11": "Prisma and TypeScript Config",
|
"11": "compilerOptions",
|
||||||
"12": "Checkout and Dashboard Components",
|
"12": "toPersian",
|
||||||
"13": "Media and Blog UI Components",
|
"13": "pets/pets.controller.ts",
|
||||||
"14": "ProductsService",
|
"14": "ProductsService",
|
||||||
"15": "Contact and Prescription Features",
|
"15": "lib/services/api.ts",
|
||||||
"16": "Development Tooling Config",
|
"16": "devDependencies",
|
||||||
"17": "Home Page Client Components",
|
"17": "useSettingsStore",
|
||||||
"18": "AppModule",
|
"18": "MetricsController",
|
||||||
"19": "B2B Inquiry Controller",
|
"19": "B2B Inquiry Controller",
|
||||||
"20": "Category Management Controller",
|
"20": "Category Management Controller",
|
||||||
"21": "Banner Management Controller",
|
"21": "RedisService",
|
||||||
"22": "Media Upload Controller",
|
"22": "MediaController",
|
||||||
"23": "Ingredient Management Controller",
|
"23": "Ingredient Management Controller",
|
||||||
"24": "Admin Dashboard Components",
|
"24": "adminRoutes.tsx",
|
||||||
"25": "ReportsController",
|
"25": "admin.module.ts",
|
||||||
"26": "Pet DTOs and Controller",
|
"26": "api",
|
||||||
"27": "Prescription Review Controller",
|
"27": "Prescription Review Controller",
|
||||||
"28": "Smart Advisor Controller",
|
"28": "Smart Advisor Controller",
|
||||||
"29": "Testimonials Management Controller",
|
"29": "Testimonials Management Controller",
|
||||||
"30": "Admin Sidebar and Layout",
|
"30": "src/services/api.ts",
|
||||||
"31": "Admin Settings Managers",
|
"31": "Spinner.tsx",
|
||||||
"32": "User Profile and Success Pages",
|
"32": "useCartStore",
|
||||||
"33": "auth.service.ts",
|
"33": "ZibalService",
|
||||||
"34": "main.ts",
|
"34": "main.ts",
|
||||||
"35": "ContactService",
|
"35": "payment.controller.ts",
|
||||||
"36": "Backend TypeScript Config",
|
"36": "Backend TypeScript Config",
|
||||||
"37": "App TypeScript Config",
|
"37": "App TypeScript Config",
|
||||||
"38": "CMS and Media Modals",
|
"38": "ConfirmModal.tsx",
|
||||||
"39": "dependencies",
|
"39": "dependencies",
|
||||||
"40": "Node TypeScript Config",
|
"40": "Node TypeScript Config",
|
||||||
"41": "Admin Blog Controller",
|
"41": "Admin Blog Controller",
|
||||||
"42": "WholesaleController",
|
"42": "WholesaleService",
|
||||||
"43": "Frontend Testing and Styles",
|
"43": "devDependencies",
|
||||||
"44": "Generic CRUD Controller",
|
"44": "Generic CRUD Controller",
|
||||||
"45": "seo.module.ts",
|
"45": "seo.module.ts",
|
||||||
"46": "Coupon Management UI",
|
"46": "Coupon Management UI",
|
||||||
"47": "Project Build Scripts",
|
"47": "Project Build Scripts",
|
||||||
"48": "Home Data Module",
|
"48": "Home Data Module",
|
||||||
"49": "Linting and PostCSS Config",
|
"49": "devDependencies",
|
||||||
"50": "Database Seeding Logic",
|
"50": "Database Seeding Logic",
|
||||||
"51": "Prisma Database Migrations",
|
"51": "Prisma Database Migrations",
|
||||||
"52": "Frontend Core Dependencies",
|
"52": "dependencies",
|
||||||
"53": "UI Skeleton and Tables",
|
"53": "UI Skeleton and Tables",
|
||||||
"54": "Shop Loading States",
|
"54": "components/Skeleton.tsx",
|
||||||
"55": "Product Detail Page",
|
"55": "BE-001",
|
||||||
"56": "Video Gallery Components",
|
"56": "VetGallery.tsx",
|
||||||
"57": "NPM Lifecycle Scripts",
|
"57": "NPM Lifecycle Scripts",
|
||||||
"58": "Pet Management API",
|
"58": "Pet Management API",
|
||||||
"59": "PrismaService",
|
"59": "PrismaService",
|
||||||
"60": "Jest Testing Config",
|
"60": "Jest Testing Config",
|
||||||
"61": "PaginationDto",
|
"61": "PaginationDto",
|
||||||
"62": "BlogsController",
|
"62": "WikiController",
|
||||||
"63": "WholesaleApplyDto",
|
"63": "FE-001",
|
||||||
"64": "SmsService",
|
"64": "PetsService",
|
||||||
"65": "rules/graphify.md",
|
"65": "rules/graphify.md",
|
||||||
"66": "App Health Controller",
|
"66": "App Health Controller",
|
||||||
"67": "Frontend UI Dependencies",
|
"67": "dependencies",
|
||||||
"68": "Pet Business Logic",
|
"68": "OrdersService",
|
||||||
"69": "Integrity Validation Scripts",
|
"69": "Integrity Validation Scripts",
|
||||||
"70": "Admin Panel Package Config",
|
"70": "Admin Panel Package Config",
|
||||||
"71": "Analytics and Report Charts",
|
"71": "Analytics and Report Charts",
|
||||||
"72": "Task Orchestration Scripts",
|
"72": "Task Orchestration Scripts",
|
||||||
"73": "Backend Package Config",
|
"73": "Backend Package Config",
|
||||||
"74": "Health Log DTOs",
|
"74": "CreateHealthLogDto",
|
||||||
"75": "React Error Boundary",
|
"75": "React Error Boundary",
|
||||||
"76": "Application Package Config",
|
"76": "Application Package Config",
|
||||||
"77": ".findAll",
|
"77": ".findAll",
|
||||||
@ -81,7 +81,7 @@
|
|||||||
"79": "VPN Utility Scripts",
|
"79": "VPN Utility Scripts",
|
||||||
"80": "NestJS CLI Config",
|
"80": "NestJS CLI Config",
|
||||||
"81": "Seed TypeScript Config",
|
"81": "Seed TypeScript Config",
|
||||||
"82": "Order Business Logic",
|
"82": "ADM-001",
|
||||||
"83": "Browser Utility Scripts",
|
"83": "Browser Utility Scripts",
|
||||||
"84": "Dev Server Startup",
|
"84": "Dev Server Startup",
|
||||||
"85": "Architectural Audit Findings",
|
"85": "Architectural Audit Findings",
|
||||||
@ -91,8 +91,8 @@
|
|||||||
"89": "Evidence Validation Scripts",
|
"89": "Evidence Validation Scripts",
|
||||||
"90": "Blog Listing Page",
|
"90": "Blog Listing Page",
|
||||||
"91": "Blog Post Detail Page",
|
"91": "Blog Post Detail Page",
|
||||||
"92": "Node Type Definitions",
|
"92": "@types/node",
|
||||||
"93": "TypeScript Language",
|
"93": "typescript",
|
||||||
"94": "UI Text Seeding",
|
"94": "UI Text Seeding",
|
||||||
"95": "Wiki Terms Seeding",
|
"95": "Wiki Terms Seeding",
|
||||||
"96": "Blog Management DTOs",
|
"96": "Blog Management DTOs",
|
||||||
@ -101,8 +101,8 @@
|
|||||||
"99": "Wiki Page Routing",
|
"99": "Wiki Page Routing",
|
||||||
"100": "Network Status Banner",
|
"100": "Network Status Banner",
|
||||||
"101": "Docker Deployment Scripts",
|
"101": "Docker Deployment Scripts",
|
||||||
"102": "ESLint Core Configuration",
|
"102": "DB-001",
|
||||||
"103": "TypeScript ESLint Support",
|
"103": "CreateReminderDto",
|
||||||
"104": "Database Migration Scripts",
|
"104": "Database Migration Scripts",
|
||||||
"105": "Scientific Terms Schema",
|
"105": "Scientific Terms Schema",
|
||||||
"106": "Blog Data Seeding",
|
"106": "Blog Data Seeding",
|
||||||
@ -116,29 +116,29 @@
|
|||||||
"114": "Manifest Data Generation",
|
"114": "Manifest Data Generation",
|
||||||
"115": "Honest Manifest Synchronization",
|
"115": "Honest Manifest Synchronization",
|
||||||
"116": "Manifest Entry Synchronization",
|
"116": "Manifest Entry Synchronization",
|
||||||
"117": "Pet Reminder DTOs",
|
"117": "Videos.tsx",
|
||||||
"118": "React DOM Library",
|
"118": "TS-001",
|
||||||
"119": "Zustand State Management",
|
"119": "TEST-001",
|
||||||
"120": "Tailwind PostCSS Plugin",
|
"120": "@tailwindcss/postcss",
|
||||||
"121": "React DOM Type Definitions",
|
"121": "@types/react-dom",
|
||||||
"122": "Admin Panel TSConfig",
|
"122": "Admin Panel TSConfig",
|
||||||
"123": "About Page Component",
|
"123": "About Page Component",
|
||||||
"124": "Privacy Page Component",
|
"124": "Privacy Page Component",
|
||||||
"125": "Next.js Security Configuration",
|
"125": "Next.js Security Configuration",
|
||||||
"126": "Typography and Font Assets",
|
"126": "Typography and Font Assets",
|
||||||
"127": "Bcrypt Password Hashing",
|
"127": "DEVOPS-001",
|
||||||
"128": "Helmet Security Middleware",
|
"128": "DOC-001",
|
||||||
"129": "What You Must Do When Invoked",
|
"129": "What You Must Do When Invoked",
|
||||||
"130": "NestJS Core Framework",
|
"130": "RolesGuard",
|
||||||
"131": "NestJS JWT Authentication",
|
"131": "راهنمای تست سیستم (Software Testing)",
|
||||||
"132": "NestJS Swagger Documentation",
|
"132": "Role & Core Objective",
|
||||||
"133": "NestJS Rate Limiting",
|
"133": "Required Review Group Closures",
|
||||||
"134": "Passport JWT Strategy",
|
"134": "Operational Rules & Boundaries",
|
||||||
"135": "Prisma Client Library",
|
"135": "Operational Rules & Boundaries",
|
||||||
"136": "Swagger UI Express",
|
"136": "🏢 AI Software Agency — Master Orchestration Protocol v3",
|
||||||
"137": "NestJS CLI Tooling",
|
"137": "@nestjs/cli",
|
||||||
"138": "NestJS Testing Utilities",
|
"138": "Operational Rules & Boundaries",
|
||||||
"139": "TypeScript Jest Integration",
|
"139": "Operational Rules & Boundaries",
|
||||||
"140": "Bcrypt Type Definitions",
|
"140": "Bcrypt Type Definitions",
|
||||||
"141": "Passport JWT Type Definitions",
|
"141": "Passport JWT Type Definitions",
|
||||||
"142": "Supertest Type Definitions",
|
"142": "Supertest Type Definitions",
|
||||||
@ -176,7 +176,7 @@
|
|||||||
"174": "Production Docker Setup",
|
"174": "Production Docker Setup",
|
||||||
"175": "Staging Docker Setup",
|
"175": "Staging Docker Setup",
|
||||||
"176": ".agents/workflows/graphify.md",
|
"176": ".agents/workflows/graphify.md",
|
||||||
"177": "BlogsService",
|
"177": "Role & Core Objective",
|
||||||
"178": "graphify reference: extra exports and benchmark",
|
"178": "graphify reference: extra exports and benchmark",
|
||||||
"179": "graphify reference: query, path, explain",
|
"179": "graphify reference: query, path, explain",
|
||||||
"180": "graphify reference: add a URL and watch a folder",
|
"180": "graphify reference: add a URL and watch a folder",
|
||||||
@ -184,12 +184,87 @@
|
|||||||
"182": "graphify reference: incremental update and cluster-only",
|
"182": "graphify reference: incremental update and cluster-only",
|
||||||
"183": "graphify reference: GitHub clone and cross-repo merge",
|
"183": "graphify reference: GitHub clone and cross-repo merge",
|
||||||
"184": "graphify reference: transcribe video and audio",
|
"184": "graphify reference: transcribe video and audio",
|
||||||
"185": "AGENTS.md",
|
"185": "Reconciled Audit Roles & Assignments",
|
||||||
"186": "instructions.md",
|
"186": "instructions.md",
|
||||||
"187": "js-yaml",
|
"187": "Phase 3.1 — Human Review Preparation and Master Backlog Critique",
|
||||||
"188": "CLAUDE.md",
|
"188": "CLAUDE.md",
|
||||||
"189": ".claude/CLAUDE.md",
|
"189": ".claude/CLAUDE.md",
|
||||||
"190": "extraction-spec.md",
|
"190": "extraction-spec.md",
|
||||||
"191": "User Login API",
|
"191": "User Login API",
|
||||||
"192": "Developer Standards and Architecture"
|
"192": "Developer Standards and Architecture",
|
||||||
|
"193": "Deep Audit Summary Report",
|
||||||
|
"194": "Operational Rules & Boundaries",
|
||||||
|
"195": "Comprehensive Change Log",
|
||||||
|
"196": "Operational Rules & Boundaries",
|
||||||
|
"197": "1. Summary of Integrity Repairs Performed",
|
||||||
|
"198": "Operational Rules & Boundaries",
|
||||||
|
"199": "Operational Rules & Boundaries",
|
||||||
|
"200": "Operational Rules & Boundaries",
|
||||||
|
"201": "Vazirmatn Changelog",
|
||||||
|
"202": "Vazirmatn Font فونت وزیرمتن",
|
||||||
|
"203": "Operational Rules & Boundaries",
|
||||||
|
"204": "backend/README.md",
|
||||||
|
"205": "Repository Map",
|
||||||
|
"206": "Sahel-Font",
|
||||||
|
"207": "AuthService",
|
||||||
|
"208": "Sahel-Font",
|
||||||
|
"209": "Role & Core Objective",
|
||||||
|
"210": "exclude",
|
||||||
|
"211": "Phase 2 Final Quality Gate Summary Report",
|
||||||
|
"212": "Task Modifications Log",
|
||||||
|
"213": "Install",
|
||||||
|
"214": "BlogsController",
|
||||||
|
"215": "System Discovery",
|
||||||
|
"216": "Product Requirement Document (PRD)",
|
||||||
|
"217": "Baseline Command Plan & Reconciled Command History",
|
||||||
|
"218": "Architecture Specification",
|
||||||
|
"219": "Project Health Audit Report",
|
||||||
|
"220": "Open Questions",
|
||||||
|
"221": "Final Phase 2 Audit Closure Report",
|
||||||
|
"222": "📝 Active Agent Working Scratchpad",
|
||||||
|
"223": "🔍 Code Health Audit Review (01_auditor)",
|
||||||
|
"224": "Omitted File Inspection Report",
|
||||||
|
"225": "Phase 3.2 / 3.3 — Implementation Readiness & Architectural Finalization Report",
|
||||||
|
"226": "Phase 3 Audit Traceability Matrix",
|
||||||
|
"227": "API Contract Specification",
|
||||||
|
"228": "⚙️ Backend Technical Review (05_dev_backend)",
|
||||||
|
"229": "🎨 Frontend & Admin Panel Technical Review (06_dev_frontend)",
|
||||||
|
"230": "🚀 SEO & Content Strategy Review (12_seo_content)",
|
||||||
|
"231": "Raw Finding Verification & Disposition Report",
|
||||||
|
"232": "React + TypeScript + Vite",
|
||||||
|
"233": "application/README.md",
|
||||||
|
"234": "🔒 Security & Performance Review (09_devops_security)",
|
||||||
|
"235": "👁️ UX & Persona Interface Review (08_visual_qa)",
|
||||||
|
"236": "Compiler Diagnostic Dispositions",
|
||||||
|
"237": "@eslint/eslintrc",
|
||||||
|
"238": "eslint-plugin-prettier",
|
||||||
|
"239": "globals",
|
||||||
|
"240": "prettier",
|
||||||
|
"241": "prisma",
|
||||||
|
"242": "supertest",
|
||||||
|
"243": "ts-loader",
|
||||||
|
"244": "ts-node",
|
||||||
|
"245": "@types/express",
|
||||||
|
"246": "@types/jest",
|
||||||
|
"247": "@types/js-yaml",
|
||||||
|
"248": "@types/multer",
|
||||||
|
"249": "eslint-plugin-react-hooks",
|
||||||
|
"250": "eslint-plugin-react-refresh",
|
||||||
|
"251": "tailwindcss",
|
||||||
|
"252": "typescript",
|
||||||
|
"253": "@testing-library/jest-dom",
|
||||||
|
"254": "@testing-library/react",
|
||||||
|
"255": "@types/react",
|
||||||
|
"256": "typescript",
|
||||||
|
"257": "vitest",
|
||||||
|
"258": "reviews/README.md",
|
||||||
|
"259": "axios",
|
||||||
|
"260": "admin-panel/public/fonts/sahel-font-v3.4.0/CHANGELOG.md",
|
||||||
|
"261": "shabnam-font-v5.0.1/CHANGELOG.md",
|
||||||
|
"262": "application/CLAUDE.md",
|
||||||
|
"263": "application/public/fonts/sahel-font-v3.4.0/CHANGELOG.md",
|
||||||
|
"264": "tailwindcss",
|
||||||
|
"265": "typescript-eslint",
|
||||||
|
"268": "wholesale.controller.ts",
|
||||||
|
"273": "AGENTS.md"
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,26 +1,26 @@
|
|||||||
# Graph Report - canina (2026-08-16)
|
# Graph Report - canina (2026-08-16)
|
||||||
|
|
||||||
## Corpus Check
|
## Corpus Check
|
||||||
- 459 files · ~667,716 words
|
- 463 files · ~678,911 words
|
||||||
- Verdict: corpus is large enough that graph structure adds value.
|
- Verdict: corpus is large enough that graph structure adds value.
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
- 3167 nodes · 5051 edges · 265 communities (171 shown, 94 thin omitted)
|
- 3234 nodes · 5269 edges · 285 communities (172 shown, 113 thin omitted)
|
||||||
- Extraction: 97% EXTRACTED · 3% INFERRED · 0% AMBIGUOUS · INFERRED: 160 edges (avg confidence: 0.79)
|
- Extraction: 96% EXTRACTED · 4% INFERRED · 0% AMBIGUOUS · INFERRED: 186 edges (avg confidence: 0.79)
|
||||||
- Token cost: 0 input · 0 output
|
- Token cost: 0 input · 0 output
|
||||||
|
|
||||||
## Graph Freshness
|
## Graph Freshness
|
||||||
- Built from commit: `03900e72`
|
- Built from commit: `8de13513`
|
||||||
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
|
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
|
||||||
- Run `graphify update .` after code changes (no API cost).
|
- Run `graphify update .` after code changes (no API cost).
|
||||||
|
|
||||||
## Community Hubs (Navigation)
|
## Community Hubs (Navigation)
|
||||||
- AdminService
|
- AdminService
|
||||||
- Pet Management Controller
|
- pets/pets.controller.ts
|
||||||
- SettingsController
|
- Roles
|
||||||
- UsersService
|
- UsersService
|
||||||
- CMS Content Management
|
- CmsController
|
||||||
- OrdersService
|
- BannersService
|
||||||
- auth.controller.ts
|
- auth.controller.ts
|
||||||
- app.module.ts
|
- app.module.ts
|
||||||
- CreateVideoDto
|
- CreateVideoDto
|
||||||
@ -28,32 +28,32 @@
|
|||||||
- SmartAdvisor.tsx
|
- SmartAdvisor.tsx
|
||||||
- compilerOptions
|
- compilerOptions
|
||||||
- toPersian
|
- toPersian
|
||||||
- Media and Blog UI Components
|
- SmsService
|
||||||
- ProductsService
|
- ProductsService
|
||||||
- lib/services/api.ts
|
- lib/services/api.ts
|
||||||
- devDependencies
|
- eslint
|
||||||
- useSettingsStore
|
- useSettingsStore
|
||||||
- AppModule
|
- AppModule
|
||||||
- B2B Inquiry Controller
|
- B2B Inquiry Controller
|
||||||
- Category Management Controller
|
- Category Management Controller
|
||||||
- Banner Management Controller
|
- auth.service.ts
|
||||||
- Media Upload Controller
|
- MediaController
|
||||||
- Ingredient Management Controller
|
- Ingredient Management Controller
|
||||||
- Admin Dashboard Components
|
- adminRoutes.tsx
|
||||||
- admin.module.ts
|
- admin.module.ts
|
||||||
- Pet DTOs and Controller
|
- ConfirmModal.tsx
|
||||||
- Prescription Review Controller
|
- Prescription Review Controller
|
||||||
- Smart Advisor Controller
|
- Smart Advisor Controller
|
||||||
- Testimonials Management Controller
|
- Testimonials Management Controller
|
||||||
- Admin Sidebar and Layout
|
- src/services/api.ts
|
||||||
- Admin Settings Managers
|
- Spinner.tsx
|
||||||
- useCartStore
|
- useCartStore
|
||||||
- ZibalService
|
- ZibalService
|
||||||
- main.ts
|
- main.ts
|
||||||
- ContactService
|
- ContactService
|
||||||
- Backend TypeScript Config
|
- Backend TypeScript Config
|
||||||
- App TypeScript Config
|
- App TypeScript Config
|
||||||
- CMS and Media Modals
|
- Coupons.tsx
|
||||||
- dependencies
|
- dependencies
|
||||||
- Node TypeScript Config
|
- Node TypeScript Config
|
||||||
- Admin Blog Controller
|
- Admin Blog Controller
|
||||||
@ -61,7 +61,7 @@
|
|||||||
- devDependencies
|
- devDependencies
|
||||||
- Generic CRUD Controller
|
- Generic CRUD Controller
|
||||||
- seo.module.ts
|
- seo.module.ts
|
||||||
- Coupon Management UI
|
- OrdersController
|
||||||
- Project Build Scripts
|
- Project Build Scripts
|
||||||
- Home Data Module
|
- Home Data Module
|
||||||
- devDependencies
|
- devDependencies
|
||||||
@ -77,19 +77,19 @@
|
|||||||
- PrismaService
|
- PrismaService
|
||||||
- Jest Testing Config
|
- Jest Testing Config
|
||||||
- PaginationDto
|
- PaginationDto
|
||||||
- BlogsController
|
- SettingsService
|
||||||
- FE-001
|
- FE-001
|
||||||
- pagination.dto.ts
|
- payment.module.ts
|
||||||
- rules/graphify.md
|
- rules/graphify.md
|
||||||
- App Health Controller
|
- App Health Controller
|
||||||
- dependencies
|
- dependencies
|
||||||
- Pet Business Logic
|
- OrdersService
|
||||||
- Integrity Validation Scripts
|
- Integrity Validation Scripts
|
||||||
- Admin Panel Package Config
|
- Admin Panel Package Config
|
||||||
- Analytics and Report Charts
|
- Analytics and Report Charts
|
||||||
- Task Orchestration Scripts
|
- Task Orchestration Scripts
|
||||||
- Backend Package Config
|
- Backend Package Config
|
||||||
- Health Log DTOs
|
- BlogsController
|
||||||
- React Error Boundary
|
- React Error Boundary
|
||||||
- Application Package Config
|
- Application Package Config
|
||||||
- WikiController
|
- WikiController
|
||||||
@ -108,7 +108,7 @@
|
|||||||
- Blog Listing Page
|
- Blog Listing Page
|
||||||
- Blog Post Detail Page
|
- Blog Post Detail Page
|
||||||
- @types/node
|
- @types/node
|
||||||
- typescript
|
- devDependencies
|
||||||
- UI Text Seeding
|
- UI Text Seeding
|
||||||
- Wiki Terms Seeding
|
- Wiki Terms Seeding
|
||||||
- Blog Management DTOs
|
- Blog Management DTOs
|
||||||
@ -118,7 +118,7 @@
|
|||||||
- Network Status Banner
|
- Network Status Banner
|
||||||
- Docker Deployment Scripts
|
- Docker Deployment Scripts
|
||||||
- DB-001
|
- DB-001
|
||||||
- typescript-eslint
|
- BlogsService
|
||||||
- Database Migration Scripts
|
- Database Migration Scripts
|
||||||
- Scientific Terms Schema
|
- Scientific Terms Schema
|
||||||
- Blog Data Seeding
|
- Blog Data Seeding
|
||||||
@ -132,10 +132,10 @@
|
|||||||
- Manifest Data Generation
|
- Manifest Data Generation
|
||||||
- Honest Manifest Synchronization
|
- Honest Manifest Synchronization
|
||||||
- Manifest Entry Synchronization
|
- Manifest Entry Synchronization
|
||||||
- Pet Reminder DTOs
|
- WikiService
|
||||||
- TS-001
|
- TS-001
|
||||||
- TEST-001
|
- TEST-001
|
||||||
- @tailwindcss/postcss
|
- ValidateCouponDto
|
||||||
- @types/react-dom
|
- @types/react-dom
|
||||||
- Admin Panel TSConfig
|
- Admin Panel TSConfig
|
||||||
- About Page Component
|
- About Page Component
|
||||||
@ -145,19 +145,19 @@
|
|||||||
- DEVOPS-001
|
- DEVOPS-001
|
||||||
- DOC-001
|
- DOC-001
|
||||||
- What You Must Do When Invoked
|
- What You Must Do When Invoked
|
||||||
- Roles
|
- JwtAuthGuard
|
||||||
- راهنمای تست سیستم (Software Testing)
|
- راهنمای تست سیستم (Software Testing)
|
||||||
- Role & Core Objective
|
- Role & Core Objective
|
||||||
- Required Review Group Closures
|
- Required Review Group Closures
|
||||||
- Operational Rules & Boundaries
|
- Operational Rules & Boundaries
|
||||||
- Operational Rules & Boundaries
|
- Operational Rules & Boundaries
|
||||||
- 🏢 AI Software Agency — Master Orchestration Protocol v3
|
- 🏢 AI Software Agency — Master Orchestration Protocol v3
|
||||||
- NestJS CLI Tooling
|
- @nestjs/cli
|
||||||
- Operational Rules & Boundaries
|
- Operational Rules & Boundaries
|
||||||
- Operational Rules & Boundaries
|
- Operational Rules & Boundaries
|
||||||
- Bcrypt Type Definitions
|
- Bcrypt Type Definitions
|
||||||
- Passport JWT Type Definitions
|
- bcryptjs
|
||||||
- Supertest Type Definitions
|
- helmet
|
||||||
- Blog Entity Model
|
- Blog Entity Model
|
||||||
- Home Entity Model
|
- Home Entity Model
|
||||||
- Wiki Entity Model
|
- Wiki Entity Model
|
||||||
@ -246,7 +246,7 @@
|
|||||||
- 👁️ UX & Persona Interface Review (08_visual_qa)
|
- 👁️ UX & Persona Interface Review (08_visual_qa)
|
||||||
- Compiler Diagnostic Dispositions
|
- Compiler Diagnostic Dispositions
|
||||||
- @eslint/eslintrc
|
- @eslint/eslintrc
|
||||||
- eslint-plugin-prettier
|
- js-yaml
|
||||||
- globals
|
- globals
|
||||||
- prettier
|
- prettier
|
||||||
- prisma
|
- prisma
|
||||||
@ -266,18 +266,38 @@
|
|||||||
- @types/react
|
- @types/react
|
||||||
- typescript
|
- typescript
|
||||||
- vitest
|
- vitest
|
||||||
|
- typescript-eslint
|
||||||
|
- @nestjs/core
|
||||||
|
- @nestjs/jwt
|
||||||
|
- @nestjs/swagger
|
||||||
|
- @nestjs/throttler
|
||||||
|
- passport-jwt
|
||||||
|
- @prisma/client
|
||||||
|
- swagger-ui-express
|
||||||
|
- AGENTS.md
|
||||||
|
- eslint-config-prettier
|
||||||
|
- @eslint/js
|
||||||
|
- jest
|
||||||
|
- @nestjs/schematics
|
||||||
|
- @nestjs/testing
|
||||||
|
- source-map-support
|
||||||
|
- ts-jest
|
||||||
|
- tsconfig-paths
|
||||||
|
- @types/bcryptjs
|
||||||
|
- typescript-eslint
|
||||||
|
- globals
|
||||||
|
|
||||||
## God Nodes (most connected - your core abstractions)
|
## God Nodes (most connected - your core abstractions)
|
||||||
1. `PrismaService` - 72 edges
|
1. `PrismaService` - 74 edges
|
||||||
2. `Roles()` - 45 edges
|
2. `Roles()` - 59 edges
|
||||||
3. `PaginationDto` - 39 edges
|
3. `PaginationDto` - 39 edges
|
||||||
4. `useSettingsStore` - 33 edges
|
4. `SmsService` - 38 edges
|
||||||
5. `api` - 32 edges
|
5. `api` - 34 edges
|
||||||
6. `AdminService` - 31 edges
|
6. `useSettingsStore` - 33 edges
|
||||||
7. `toPersian()` - 31 edges
|
7. `AdminService` - 31 edges
|
||||||
8. `AdminController` - 30 edges
|
8. `toPersian()` - 31 edges
|
||||||
9. `useCartStore` - 29 edges
|
9. `AdminController` - 30 edges
|
||||||
10. `JwtAuthGuard` - 27 edges
|
10. `useCartStore` - 29 edges
|
||||||
|
|
||||||
## Surprising Connections (you probably didn't know these)
|
## Surprising Connections (you probably didn't know these)
|
||||||
- `User Profile Photo` --conceptually_related_to--> `User Roles and Capabilities` [INFERRED]
|
- `User Profile Photo` --conceptually_related_to--> `User Roles and Capabilities` [INFERRED]
|
||||||
@ -292,47 +312,47 @@
|
|||||||
backend/src/orders/orders.controller.ts → frontend/admin-panel/src/types/admin.ts
|
backend/src/orders/orders.controller.ts → frontend/admin-panel/src/types/admin.ts
|
||||||
|
|
||||||
## Import Cycles
|
## Import Cycles
|
||||||
- 3-file cycle: `frontend/application/lib/services/api.ts -> frontend/application/lib/store/userStore.ts -> frontend/application/lib/services/authService.ts -> frontend/application/lib/services/api.ts`
|
|
||||||
- 3-file cycle: `frontend/application/lib/services/api.ts -> frontend/application/lib/store/userStore.ts -> frontend/application/lib/store/cartStore.ts -> frontend/application/lib/services/api.ts`
|
- 3-file cycle: `frontend/application/lib/services/api.ts -> frontend/application/lib/store/userStore.ts -> frontend/application/lib/store/cartStore.ts -> frontend/application/lib/services/api.ts`
|
||||||
|
- 3-file cycle: `frontend/application/lib/services/api.ts -> frontend/application/lib/store/userStore.ts -> frontend/application/lib/services/authService.ts -> frontend/application/lib/services/api.ts`
|
||||||
- 4-file cycle: `frontend/application/lib/services/api.ts -> frontend/application/lib/store/userStore.ts -> frontend/application/lib/store/cartStore.ts -> frontend/application/lib/services/orderService.ts -> frontend/application/lib/services/api.ts`
|
- 4-file cycle: `frontend/application/lib/services/api.ts -> frontend/application/lib/store/userStore.ts -> frontend/application/lib/store/cartStore.ts -> frontend/application/lib/services/orderService.ts -> frontend/application/lib/services/api.ts`
|
||||||
|
|
||||||
## Hyperedges (group relationships)
|
## Hyperedges (group relationships)
|
||||||
- **Rastikerdar Font Ecosystem** — frontend_application_public_fonts_shabnam_font_v5_0_1_readme, frontend_application_public_fonts_vazirmatn_v33_003_readme, vazir_font [EXTRACTED 0.95]
|
- **Rastikerdar Font Ecosystem** — frontend_application_public_fonts_shabnam_font_v5_0_1_readme, frontend_application_public_fonts_vazirmatn_v33_003_readme, vazir_font [EXTRACTED 0.95]
|
||||||
- **Canina Deployment Stack** — scripts_compose_prod, scripts_compose_stage [EXTRACTED 1.00]
|
- **Canina Deployment Stack** — scripts_compose_prod, scripts_compose_stage [EXTRACTED 1.00]
|
||||||
|
|
||||||
## Communities (265 total, 94 thin omitted)
|
## Communities (285 total, 113 thin omitted)
|
||||||
|
|
||||||
### Community 0 - "AdminService"
|
### Community 0 - "AdminService"
|
||||||
Cohesion: 0.07
|
Cohesion: 0.07
|
||||||
Nodes (22): AdminController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+14 more)
|
Nodes (22): AdminController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+14 more)
|
||||||
|
|
||||||
### Community 1 - "Pet Management Controller"
|
### Community 1 - "pets/pets.controller.ts"
|
||||||
Cohesion: 0.17
|
Cohesion: 0.05
|
||||||
Nodes (18): PetsController, ApiBadRequestResponse, ApiBearerAuth, ApiCreatedResponse, ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiTags (+10 more)
|
Nodes (45): CreateHealthLogDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, CreatePetDto, ApiProperty (+37 more)
|
||||||
|
|
||||||
### Community 2 - "SettingsController"
|
### Community 2 - "Roles"
|
||||||
Cohesion: 0.13
|
Cohesion: 0.19
|
||||||
Nodes (16): SettingsController, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body, Controller, Delete (+8 more)
|
Nodes (16): Roles(), SettingsController, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+8 more)
|
||||||
|
|
||||||
### Community 3 - "UsersService"
|
### Community 3 - "UsersService"
|
||||||
Cohesion: 0.06
|
Cohesion: 0.07
|
||||||
Nodes (37): AuthModule, Module, JwtPayload, JwtStrategy, Injectable, AddressDto, ApiProperty, ApiPropertyOptional (+29 more)
|
Nodes (33): JwtPayload, JwtStrategy, Injectable, AddressDto, ApiProperty, ApiPropertyOptional, IsBoolean, IsNotEmpty (+25 more)
|
||||||
|
|
||||||
### Community 4 - "CMS Content Management"
|
### Community 4 - "CmsController"
|
||||||
Cohesion: 0.09
|
Cohesion: 0.09
|
||||||
Nodes (24): CmsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+16 more)
|
Nodes (24): CmsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+16 more)
|
||||||
|
|
||||||
### Community 5 - "OrdersService"
|
### Community 5 - "BannersService"
|
||||||
Cohesion: 0.07
|
Cohesion: 0.13
|
||||||
Nodes (29): CreateOrderDto, OrderItemDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional (+21 more)
|
Nodes (15): BannersController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+7 more)
|
||||||
|
|
||||||
### Community 6 - "auth.controller.ts"
|
### Community 6 - "auth.controller.ts"
|
||||||
Cohesion: 0.05
|
Cohesion: 0.07
|
||||||
Nodes (41): AuthController, ApiBadRequestResponse, ApiOkResponse, ApiOperation, ApiTags, Body, Controller, Post (+33 more)
|
Nodes (39): AuthController, ApiBadRequestResponse, ApiOkResponse, ApiOperation, ApiTags, Body, Controller, Post (+31 more)
|
||||||
|
|
||||||
### Community 7 - "app.module.ts"
|
### Community 7 - "app.module.ts"
|
||||||
Cohesion: 0.09
|
Cohesion: 0.10
|
||||||
Nodes (28): B2BModule, Module, BannersModule, Module, CmsModule, Module, SmsModule, Global (+20 more)
|
Nodes (25): AuthModule, Module, B2BModule, Module, BannersModule, Module, CmsModule, Module (+17 more)
|
||||||
|
|
||||||
### Community 8 - "CreateVideoDto"
|
### Community 8 - "CreateVideoDto"
|
||||||
Cohesion: 0.07
|
Cohesion: 0.07
|
||||||
@ -354,10 +374,6 @@ Nodes (32): compilerOptions, allowJs, esModuleInterop, incremental, isolatedModu
|
|||||||
Cohesion: 0.08
|
Cohesion: 0.08
|
||||||
Nodes (37): ClientLayout(), VerifyContent(), AddressModal(), AddressModalProps, AuthModal(), AuthModalProps, B2BPortal(), BackButton() (+29 more)
|
Nodes (37): ClientLayout(), VerifyContent(), AddressModal(), AddressModalProps, AuthModal(), AuthModalProps, B2BPortal(), BackButton() (+29 more)
|
||||||
|
|
||||||
### Community 13 - "Media and Blog UI Components"
|
|
||||||
Cohesion: 0.09
|
|
||||||
Nodes (19): Media, MediaSelector(), MediaSelectorProps, Pagination(), PaginationProps, BlogPost, Category, Pet (+11 more)
|
|
||||||
|
|
||||||
### Community 14 - "ProductsService"
|
### Community 14 - "ProductsService"
|
||||||
Cohesion: 0.09
|
Cohesion: 0.09
|
||||||
Nodes (19): GetProductsDto, ApiPropertyOptional, IsEnum, IsOptional, IsString, ProductsController, ApiOperation, ApiTags (+11 more)
|
Nodes (19): GetProductsDto, ApiPropertyOptional, IsEnum, IsOptional, IsString, ProductsController, ApiOperation, ApiTags (+11 more)
|
||||||
@ -366,18 +382,10 @@ Nodes (19): GetProductsDto, ApiPropertyOptional, IsEnum, IsOptional, IsString, P
|
|||||||
Cohesion: 0.08
|
Cohesion: 0.08
|
||||||
Nodes (17): metadata, ContactFormClient(), ContactInfoItem, PrescriptionUploadModal(), PrescriptionUploadModalProps, api, ApiErrorPayload, ApiErr (+9 more)
|
Nodes (17): metadata, ContactFormClient(), ContactInfoItem, PrescriptionUploadModal(), PrescriptionUploadModalProps, api, ApiErrorPayload, ApiErr (+9 more)
|
||||||
|
|
||||||
### Community 16 - "devDependencies"
|
|
||||||
Cohesion: 0.09
|
|
||||||
Nodes (23): devDependencies, eslint, eslint-config-prettier, @eslint/js, jest, @nestjs/schematics, @nestjs/testing, source-map-support (+15 more)
|
|
||||||
|
|
||||||
### Community 17 - "useSettingsStore"
|
### Community 17 - "useSettingsStore"
|
||||||
Cohesion: 0.07
|
Cohesion: 0.07
|
||||||
Nodes (30): HomeClient(), HomeClientProps, getHomeData(), Home(), metadata, metadata, EnamadBadge(), Footer() (+22 more)
|
Nodes (30): HomeClient(), HomeClientProps, getHomeData(), Home(), metadata, metadata, EnamadBadge(), Footer() (+22 more)
|
||||||
|
|
||||||
### Community 18 - "AppModule"
|
|
||||||
Cohesion: 0.12
|
|
||||||
Nodes (7): ApiExcludeController, AppModule, Module, MetricsController, Controller, Get, Res
|
|
||||||
|
|
||||||
### Community 19 - "B2B Inquiry Controller"
|
### Community 19 - "B2B Inquiry Controller"
|
||||||
Cohesion: 0.13
|
Cohesion: 0.13
|
||||||
Nodes (15): B2BController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param (+7 more)
|
Nodes (15): B2BController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param (+7 more)
|
||||||
@ -386,29 +394,29 @@ Nodes (15): B2BController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controlle
|
|||||||
Cohesion: 0.10
|
Cohesion: 0.10
|
||||||
Nodes (16): CategoriesController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+8 more)
|
Nodes (16): CategoriesController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+8 more)
|
||||||
|
|
||||||
### Community 21 - "Banner Management Controller"
|
### Community 21 - "auth.service.ts"
|
||||||
Cohesion: 0.13
|
Cohesion: 0.10
|
||||||
Nodes (15): BannersController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+7 more)
|
Nodes (10): AdminLoginInput, AuthService, LoginInput, RegisterInput, Injectable, RedisModule, Global, Module (+2 more)
|
||||||
|
|
||||||
### Community 22 - "Media Upload Controller"
|
### Community 22 - "MediaController"
|
||||||
Cohesion: 0.11
|
Cohesion: 0.10
|
||||||
Nodes (16): MediaController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+8 more)
|
Nodes (16): MediaController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+8 more)
|
||||||
|
|
||||||
### Community 23 - "Ingredient Management Controller"
|
### Community 23 - "Ingredient Management Controller"
|
||||||
Cohesion: 0.13
|
Cohesion: 0.13
|
||||||
Nodes (14): IngredientsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
|
Nodes (14): IngredientsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
|
||||||
|
|
||||||
### Community 24 - "Admin Dashboard Components"
|
### Community 24 - "adminRoutes.tsx"
|
||||||
Cohesion: 0.10
|
Cohesion: 0.08
|
||||||
Nodes (14): App(), ContactInfoItem, ContactSubmission, CategoryDist, DashboardData, GROUP_PAGE_MAP, GROUPS, PAGE_TABS (+6 more)
|
Nodes (18): App(), ContactInfoItem, ContactSubmission, CategoryDist, DashboardData, PatternItem, SmsConfigState, SmsLogItem (+10 more)
|
||||||
|
|
||||||
### Community 25 - "admin.module.ts"
|
### Community 25 - "admin.module.ts"
|
||||||
Cohesion: 0.07
|
Cohesion: 0.10
|
||||||
Nodes (20): AdminModule, Module, BlogQuery, BlogsService, Injectable, PetQuery, PetsService, Injectable (+12 more)
|
Nodes (14): AdminModule, Module, PetQuery, PetsService, Injectable, ReportsController, ApiBearerAuth, ApiOperation (+6 more)
|
||||||
|
|
||||||
### Community 26 - "Pet DTOs and Controller"
|
### Community 26 - "ConfirmModal.tsx"
|
||||||
Cohesion: 0.16
|
Cohesion: 0.10
|
||||||
Nodes (13): CreatePetDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional, IsString (+5 more)
|
Nodes (16): ConfirmModal(), ConfirmModalProps, HeroBanner, VetTestimonial, Pet, fetchVideosList(), Video, Videos() (+8 more)
|
||||||
|
|
||||||
### Community 27 - "Prescription Review Controller"
|
### Community 27 - "Prescription Review Controller"
|
||||||
Cohesion: 0.14
|
Cohesion: 0.14
|
||||||
@ -422,21 +430,21 @@ Nodes (14): SmartAdvisorController, ApiBearerAuth, ApiOperation, ApiTags, Body,
|
|||||||
Cohesion: 0.13
|
Cohesion: 0.13
|
||||||
Nodes (14): TestimonialsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
|
Nodes (14): TestimonialsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
|
||||||
|
|
||||||
### Community 30 - "Admin Sidebar and Layout"
|
### Community 30 - "src/services/api.ts"
|
||||||
Cohesion: 0.17
|
Cohesion: 0.10
|
||||||
Nodes (16): Layout(), ProtectedRoute(), menuGroups, Sidebar(), SidebarProps, SEARCHABLE_PAGES, Topbar(), TopbarProps (+8 more)
|
Nodes (27): Layout(), ProtectedRoute(), menuGroups, Sidebar(), SidebarProps, SEARCHABLE_PAGES, Topbar(), TopbarProps (+19 more)
|
||||||
|
|
||||||
### Community 31 - "Admin Settings Managers"
|
### Community 31 - "Spinner.tsx"
|
||||||
Cohesion: 0.09
|
Cohesion: 0.08
|
||||||
Nodes (21): Spinner(), B2BManager, BannersManager, FinancialSettingsPage, SeoSettingsPage, SmartAdvisorManager, SystemSettingsPage, TestimonialsManager (+13 more)
|
Nodes (23): Media, MediaSelector(), MediaSelectorProps, Spinner(), BlogPost, Category, Category, Product (+15 more)
|
||||||
|
|
||||||
### Community 32 - "useCartStore"
|
### Community 32 - "useCartStore"
|
||||||
Cohesion: 0.12
|
Cohesion: 0.12
|
||||||
Nodes (16): metadata, ArchiveProductCard(), ProductCard(), Header(), MENU_ICONS, OrderSuccess(), OrderTracking(), PetProfile() (+8 more)
|
Nodes (16): metadata, ArchiveProductCard(), ProductCard(), Header(), MENU_ICONS, OrderSuccess(), OrderTracking(), PetProfile() (+8 more)
|
||||||
|
|
||||||
### Community 33 - "ZibalService"
|
### Community 33 - "ZibalService"
|
||||||
Cohesion: 0.07
|
Cohesion: 0.06
|
||||||
Nodes (30): InitiatePaymentDto, InitiateWalletTopupDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, ApiPropertyOptional (+22 more)
|
Nodes (37): AdminTransactionFilterDto, ApiPropertyOptional, IsNumber, IsOptional, IsString, Type, InitiatePaymentDto, InitiateWalletTopupDto (+29 more)
|
||||||
|
|
||||||
### Community 34 - "main.ts"
|
### Community 34 - "main.ts"
|
||||||
Cohesion: 0.14
|
Cohesion: 0.14
|
||||||
@ -454,13 +462,13 @@ Nodes (22): compilerOptions, allowSyntheticDefaultImports, baseUrl, declaration,
|
|||||||
Cohesion: 0.09
|
Cohesion: 0.09
|
||||||
Nodes (22): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, jsx, lib, module, moduleDetection, moduleResolution (+14 more)
|
Nodes (22): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, jsx, lib, module, moduleDetection, moduleResolution (+14 more)
|
||||||
|
|
||||||
### Community 38 - "CMS and Media Modals"
|
### Community 38 - "Coupons.tsx"
|
||||||
Cohesion: 0.10
|
Cohesion: 0.09
|
||||||
Nodes (16): ConfirmModal(), ConfirmModalProps, HeroBanner, VetTestimonial, Media, fetchVideosList(), Video, Videos() (+8 more)
|
Nodes (15): Pagination(), PaginationProps, Coupon, CouponFormData, CouponModalProps, CouponTarget, Media, Stats (+7 more)
|
||||||
|
|
||||||
### Community 39 - "dependencies"
|
### Community 39 - "dependencies"
|
||||||
Cohesion: 0.05
|
Cohesion: 0.10
|
||||||
Nodes (41): dependencies, bcrypt, bcryptjs, class-transformer, class-validator, helmet, ioredis, js-yaml (+33 more)
|
Nodes (21): dependencies, bcrypt, class-transformer, class-validator, ioredis, @nestjs/common, @nestjs/passport, @nestjs/platform-express (+13 more)
|
||||||
|
|
||||||
### Community 40 - "Node TypeScript Config"
|
### Community 40 - "Node TypeScript Config"
|
||||||
Cohesion: 0.10
|
Cohesion: 0.10
|
||||||
@ -486,9 +494,9 @@ Nodes (14): ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, De
|
|||||||
Cohesion: 0.16
|
Cohesion: 0.16
|
||||||
Nodes (10): SeoController, ApiOperation, ApiTags, Controller, Get, Param, SeoModule, Module (+2 more)
|
Nodes (10): SeoController, ApiOperation, ApiTags, Controller, Get, Param, SeoModule, Module (+2 more)
|
||||||
|
|
||||||
### Community 46 - "Coupon Management UI"
|
### Community 46 - "OrdersController"
|
||||||
Cohesion: 0.25
|
Cohesion: 0.13
|
||||||
Nodes (5): Coupon, CouponFormData, CouponModalProps, CouponTarget, Coupons
|
Nodes (16): OrdersController, ApiBadRequestResponse, ApiBearerAuth, ApiCreatedResponse, ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiTags (+8 more)
|
||||||
|
|
||||||
### Community 47 - "Project Build Scripts"
|
### Community 47 - "Project Build Scripts"
|
||||||
Cohesion: 0.11
|
Cohesion: 0.11
|
||||||
@ -500,7 +508,7 @@ Nodes (10): HomeController, ApiOkResponse, ApiOperation, ApiTags, Controller, Ge
|
|||||||
|
|
||||||
### Community 49 - "devDependencies"
|
### Community 49 - "devDependencies"
|
||||||
Cohesion: 0.11
|
Cohesion: 0.11
|
||||||
Nodes (19): autoprefixer, devDependencies, autoprefixer, eslint, @eslint/js, globals, postcss, @types/node (+11 more)
|
Nodes (19): autoprefixer, devDependencies, autoprefixer, eslint, @eslint/js, postcss, @tailwindcss/postcss, @types/node (+11 more)
|
||||||
|
|
||||||
### Community 50 - "Database Seeding Logic"
|
### Community 50 - "Database Seeding Logic"
|
||||||
Cohesion: 0.17
|
Cohesion: 0.17
|
||||||
@ -539,8 +547,8 @@ Cohesion: 0.15
|
|||||||
Nodes (11): PetsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Delete, Get (+3 more)
|
Nodes (11): PetsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Delete, Get (+3 more)
|
||||||
|
|
||||||
### Community 59 - "PrismaService"
|
### Community 59 - "PrismaService"
|
||||||
Cohesion: 0.06
|
Cohesion: 0.08
|
||||||
Nodes (21): CouponTargetInput, PaginationQuery, CategoryQuery, AdminLoginInput, LoginInput, RegisterInput, MeliPayamakResponse, SendPatternSmsOptions (+13 more)
|
Nodes (18): ApiExcludeController, CouponTargetInput, PaginationQuery, CategoryQuery, MetricsController, Controller, Get, Res (+10 more)
|
||||||
|
|
||||||
### Community 60 - "Jest Testing Config"
|
### Community 60 - "Jest Testing Config"
|
||||||
Cohesion: 0.15
|
Cohesion: 0.15
|
||||||
@ -548,19 +556,19 @@ Nodes (13): jest, collectCoverageFrom, coverageDirectory, moduleFileExtensions,
|
|||||||
|
|
||||||
### Community 61 - "PaginationDto"
|
### Community 61 - "PaginationDto"
|
||||||
Cohesion: 0.13
|
Cohesion: 0.13
|
||||||
Nodes (12): PaginationDto, ApiPropertyOptional, IsEnum, IsOptional, IsString, Type, Module, WikiModule (+4 more)
|
Nodes (13): PaginationDto, SortOrder, ApiPropertyOptional, IsEnum, IsOptional, IsString, Type, Module (+5 more)
|
||||||
|
|
||||||
### Community 62 - "BlogsController"
|
### Community 62 - "SettingsService"
|
||||||
Cohesion: 0.20
|
Cohesion: 0.11
|
||||||
Nodes (7): BlogsController, ApiTags, Controller, BlogsModule, Module, BlogsService, Injectable
|
Nodes (4): SmsLogQuery, ScientificTermData, SettingsService, Injectable
|
||||||
|
|
||||||
### Community 63 - "FE-001"
|
### Community 63 - "FE-001"
|
||||||
Cohesion: 0.06
|
Cohesion: 0.06
|
||||||
Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, Architecture Overview & Confirmed Strengths, Category, Completion Statement, Confidence (+23 more)
|
Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, Architecture Overview & Confirmed Strengths, Category, Completion Statement, Confidence (+23 more)
|
||||||
|
|
||||||
### Community 64 - "pagination.dto.ts"
|
### Community 64 - "payment.module.ts"
|
||||||
Cohesion: 0.11
|
Cohesion: 0.16
|
||||||
Nodes (10): Injectable, WikiQuery, WikiService, SortOrder, IsNotEmpty, IsNumber, IsString, ValidateCouponDto (+2 more)
|
Nodes (10): SmsModule, Global, Module, ContactModule, Module, PaymentModule, Module, ZibalInquiryResponse (+2 more)
|
||||||
|
|
||||||
### Community 66 - "App Health Controller"
|
### Community 66 - "App Health Controller"
|
||||||
Cohesion: 0.29
|
Cohesion: 0.29
|
||||||
@ -570,6 +578,10 @@ Nodes (5): AppController, Controller, Get, AppService, Injectable
|
|||||||
Cohesion: 0.12
|
Cohesion: 0.12
|
||||||
Nodes (17): dependencies, axios, lucide-react, motion, next, react, react-dom, sonner (+9 more)
|
Nodes (17): dependencies, axios, lucide-react, motion, next, react, react-dom, sonner (+9 more)
|
||||||
|
|
||||||
|
### Community 68 - "OrdersService"
|
||||||
|
Cohesion: 0.12
|
||||||
|
Nodes (15): CreateOrderDto, OrderItemDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional (+7 more)
|
||||||
|
|
||||||
### Community 69 - "Integrity Validation Scripts"
|
### Community 69 - "Integrity Validation Scripts"
|
||||||
Cohesion: 0.20
|
Cohesion: 0.20
|
||||||
Nodes (9): dispSum, errors, invalidNewIds, manifest, report, trackedFiles, uninspected, verifiedIndex (+1 more)
|
Nodes (9): dispSum, errors, invalidNewIds, manifest, report, trackedFiles, uninspected, verifiedIndex (+1 more)
|
||||||
@ -590,9 +602,9 @@ Nodes (8): cmd_next_task(), cmd_progress(), cmd_reset(), cmd_status(), cmd_unblo
|
|||||||
Cohesion: 0.22
|
Cohesion: 0.22
|
||||||
Nodes (8): author, description, license, name, prisma, seed, private, version
|
Nodes (8): author, description, license, name, prisma, seed, private, version
|
||||||
|
|
||||||
### Community 74 - "Health Log DTOs"
|
### Community 74 - "BlogsController"
|
||||||
Cohesion: 0.29
|
Cohesion: 0.20
|
||||||
Nodes (6): CreateHealthLogDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString
|
Nodes (7): BlogsController, ApiTags, Controller, BlogsModule, Module, BlogsService, Injectable
|
||||||
|
|
||||||
### Community 75 - "React Error Boundary"
|
### Community 75 - "React Error Boundary"
|
||||||
Cohesion: 0.22
|
Cohesion: 0.22
|
||||||
@ -654,6 +666,10 @@ Nodes (4): Blog(), getBlogs(), metadata, BlogPage()
|
|||||||
Cohesion: 0.60
|
Cohesion: 0.60
|
||||||
Nodes (4): BlogPostPage(), generateMetadata(), getBlog(), revalidate
|
Nodes (4): BlogPostPage(), generateMetadata(), getBlog(), revalidate
|
||||||
|
|
||||||
|
### Community 93 - "devDependencies"
|
||||||
|
Cohesion: 0.22
|
||||||
|
Nodes (9): devDependencies, eslint-plugin-prettier, @types/passport-jwt, @types/supertest, typescript, typescript, eslint-plugin-prettier, @types/passport-jwt (+1 more)
|
||||||
|
|
||||||
### Community 99 - "Wiki Page Routing"
|
### Community 99 - "Wiki Page Routing"
|
||||||
Cohesion: 0.83
|
Cohesion: 0.83
|
||||||
Nodes (3): generateMetadata(), getWikiTerm(), WikiTermPage()
|
Nodes (3): generateMetadata(), getWikiTerm(), WikiTermPage()
|
||||||
@ -666,13 +682,17 @@ Nodes (3): COMPOSE_DOCKER_CLI_BUILD, DOCKER_BUILDKIT, deploy.sh script
|
|||||||
Cohesion: 0.06
|
Cohesion: 0.06
|
||||||
Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, Category, Completion Statement, Confidence, Database and Data Integrity Audit Report (+23 more)
|
Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, Category, Completion Statement, Confidence, Database and Data Integrity Audit Report (+23 more)
|
||||||
|
|
||||||
|
### Community 103 - "BlogsService"
|
||||||
|
Cohesion: 0.22
|
||||||
|
Nodes (3): BlogQuery, BlogsService, Injectable
|
||||||
|
|
||||||
### Community 109 - "Auth Architecture and Planning"
|
### Community 109 - "Auth Architecture and Planning"
|
||||||
Cohesion: 0.67
|
Cohesion: 0.67
|
||||||
Nodes (3): ADR-AUTH-001: Admin Panel Authentication Architecture & Token Management, Master Task Backlog (Phase 3.3), Phase 3 Master Execution Plan
|
Nodes (3): ADR-AUTH-001: Admin Panel Authentication Architecture & Token Management, Master Task Backlog (Phase 3.3), Phase 3 Master Execution Plan
|
||||||
|
|
||||||
### Community 117 - "Pet Reminder DTOs"
|
### Community 117 - "WikiService"
|
||||||
Cohesion: 0.29
|
Cohesion: 0.22
|
||||||
Nodes (6): CreateReminderDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString
|
Nodes (3): Injectable, WikiQuery, WikiService
|
||||||
|
|
||||||
### Community 118 - "TS-001"
|
### Community 118 - "TS-001"
|
||||||
Cohesion: 0.06
|
Cohesion: 0.06
|
||||||
@ -682,6 +702,10 @@ Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternati
|
|||||||
Cohesion: 0.06
|
Cohesion: 0.06
|
||||||
Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, Category, Completion Statement, Confidence, Dependencies (+23 more)
|
Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, Category, Completion Statement, Confidence, Dependencies (+23 more)
|
||||||
|
|
||||||
|
### Community 120 - "ValidateCouponDto"
|
||||||
|
Cohesion: 0.50
|
||||||
|
Nodes (4): IsNotEmpty, IsNumber, IsString, ValidateCouponDto
|
||||||
|
|
||||||
### Community 126 - "Typography and Font Assets"
|
### Community 126 - "Typography and Font Assets"
|
||||||
Cohesion: 0.67
|
Cohesion: 0.67
|
||||||
Nodes (3): Shabnam Font README, Shabnam Font Sample, Vazir Font
|
Nodes (3): Shabnam Font README, Shabnam Font Sample, Vazir Font
|
||||||
@ -698,9 +722,9 @@ Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternati
|
|||||||
Cohesion: 0.07
|
Cohesion: 0.07
|
||||||
Nodes (26): For /graphify add and --watch, For /graphify query, For the commit hook and native CLAUDE.md integration, For --update and --cluster-only, /graphify, Honesty Rules, Interpreter guard for subcommands, Part A - Structural extraction for code files (+18 more)
|
Nodes (26): For /graphify add and --watch, For /graphify query, For the commit hook and native CLAUDE.md integration, For --update and --cluster-only, /graphify, Honesty Rules, Interpreter guard for subcommands, Part A - Structural extraction for code files (+18 more)
|
||||||
|
|
||||||
### Community 130 - "Roles"
|
### Community 130 - "JwtAuthGuard"
|
||||||
Cohesion: 0.21
|
Cohesion: 0.18
|
||||||
Nodes (8): JwtAuthGuard, Injectable, Roles(), ROLES_KEY, RequestWithUser, RolesGuard, Injectable, UserReqPayload
|
Nodes (7): JwtAuthGuard, Injectable, ROLES_KEY, RequestWithUser, RolesGuard, Injectable, UserReqPayload
|
||||||
|
|
||||||
### Community 131 - "راهنمای تست سیستم (Software Testing)"
|
### Community 131 - "راهنمای تست سیستم (Software Testing)"
|
||||||
Cohesion: 0.07
|
Cohesion: 0.07
|
||||||
@ -931,24 +955,24 @@ Cohesion: 0.50
|
|||||||
Nodes (3): Deploy on Vercel, Getting Started, Learn More
|
Nodes (3): Deploy on Vercel, Getting Started, Learn More
|
||||||
|
|
||||||
## Knowledge Gaps
|
## Knowledge Gaps
|
||||||
- **1170 isolated node(s):** `$schema`, `collection`, `sourceRoot`, `deleteOutDir`, `name` (+1165 more)
|
- **1179 isolated node(s):** `$schema`, `collection`, `sourceRoot`, `deleteOutDir`, `name` (+1174 more)
|
||||||
These have ≤1 connection - possible missing edges or undocumented components.
|
These have ≤1 connection - possible missing edges or undocumented components.
|
||||||
- **94 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
|
- **113 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
|
||||||
|
|
||||||
## Suggested Questions
|
## Suggested Questions
|
||||||
_Questions this graph is uniquely positioned to answer:_
|
_Questions this graph is uniquely positioned to answer:_
|
||||||
|
|
||||||
- **Why does `ApiResponse` connect `Admin Sidebar and Layout` to `Pet Management Controller`, `UsersService`, `OrdersService`, `auth.controller.ts`, `WikiController`, `ProductsService`, `Home Data Module`, `BlogsController`, `Admin Settings Managers`?**
|
- **Why does `ApiResponse` connect `src/services/api.ts` to `pets/pets.controller.ts`, `UsersService`, `auth.controller.ts`, `BlogsController`, `WikiController`, `OrdersController`, `ProductsService`, `Home Data Module`?**
|
||||||
_High betweenness centrality (0.033) - this node is a cross-community bridge._
|
_High betweenness centrality (0.039) - this node is a cross-community bridge._
|
||||||
- **Why does `Roles()` connect `Roles` to `SettingsController`, `ContactService`, `CMS Content Management`, `WholesaleApplyDto`, `ProductsService`, `B2B Inquiry Controller`, `Banner Management Controller`, `Ingredient Management Controller`, `Prescription Review Controller`, `Smart Advisor Controller`, `Testimonials Management Controller`?**
|
- **Why does `Roles()` connect `Roles` to `ZibalService`, `JwtAuthGuard`, `ContactService`, `CmsController`, `BannersService`, `WholesaleApplyDto`, `ProductsService`, `B2B Inquiry Controller`, `Ingredient Management Controller`, `Prescription Review Controller`, `Smart Advisor Controller`, `Testimonials Management Controller`, `SettingsService`?**
|
||||||
_High betweenness centrality (0.020) - this node is a cross-community bridge._
|
_High betweenness centrality (0.037) - this node is a cross-community bridge._
|
||||||
- **Why does `JwtAuthGuard` connect `Roles` to `AdminService`, `pagination.dto.ts`, `ZibalService`, `UsersService`, `CreateVideoDto`, `ProductsService`, `admin.module.ts`, `Pet DTOs and Controller`?**
|
- **Why does `JwtAuthGuard` connect `JwtAuthGuard` to `AdminService`, `pets/pets.controller.ts`, `UsersService`, `CmsController`, `OrdersService`, `CreateVideoDto`, `MediaController`, `admin.module.ts`, `SettingsService`?**
|
||||||
_High betweenness centrality (0.020) - this node is a cross-community bridge._
|
_High betweenness centrality (0.022) - this node is a cross-community bridge._
|
||||||
- **What connects `$schema`, `collection`, `sourceRoot` to the rest of the system?**
|
- **What connects `$schema`, `collection`, `sourceRoot` to the rest of the system?**
|
||||||
_1170 weakly-connected nodes found - possible documentation gaps or missing edges._
|
_1179 weakly-connected nodes found - possible documentation gaps or missing edges._
|
||||||
- **Should `AdminService` be split into smaller, more focused modules?**
|
- **Should `AdminService` be split into smaller, more focused modules?**
|
||||||
_Cohesion score 0.06690140845070422 - nodes in this community are weakly interconnected._
|
_Cohesion score 0.06841046277665996 - nodes in this community are weakly interconnected._
|
||||||
- **Should `SettingsController` be split into smaller, more focused modules?**
|
- **Should `pets/pets.controller.ts` be split into smaller, more focused modules?**
|
||||||
_Cohesion score 0.1251778093883357 - nodes in this community are weakly interconnected._
|
_Cohesion score 0.053923541247484906 - nodes in this community are weakly interconnected._
|
||||||
- **Should `UsersService` be split into smaller, more focused modules?**
|
- **Should `UsersService` be split into smaller, more focused modules?**
|
||||||
_Cohesion score 0.06093189964157706 - nodes in this community are weakly interconnected._
|
_Cohesion score 0.06766917293233082 - nodes in this community are weakly interconnected._
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"nodes": [{"id": "$graphify-root$_agents_md", "label": "AGENTS.md", "file_type": "document", "source_file": "AGENTS.md", "source_location": "L1"}, {"id": "$graphify-root$_agents_graphify", "label": "graphify", "file_type": "document", "source_file": "AGENTS.md", "source_location": "L1"}], "edges": [{"source": "$graphify-root$_agents_md", "target": "$graphify-root$_agents_graphify", "relation": "contains", "confidence": "EXTRACTED", "source_file": "AGENTS.md", "source_location": "L1", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||||
2
graphify-out/cache/stat-index.json
vendored
2
graphify-out/cache/stat-index.json
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
11638
graphify-out/graph.json
11638
graphify-out/graph.json
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user