feat(admin): implement smart pricing, rounding rules, formula margins and bulk price adjustment
This commit is contained in:
parent
382ebc0ce6
commit
ac15ece619
@ -201,6 +201,8 @@ model Product {
|
||||
wholesalePrice Decimal? @map("wholesale_price") @db.Decimal(15, 2)
|
||||
requiresRx Boolean @default(false) @map("requires_rx")
|
||||
buyPrice Decimal @default(0) @map("buy_price") @db.Decimal(15, 2)
|
||||
marginRetailPercent Decimal? @map("margin_retail_percent") @db.Decimal(6, 2)
|
||||
marginWholesalePercent Decimal? @map("margin_wholesale_percent") @db.Decimal(6, 2)
|
||||
priceDisplay String @map("price_display") @db.VarChar(50)
|
||||
unit String @db.VarChar(50)
|
||||
packageSize Decimal @map("package_size") @db.Decimal(10, 2)
|
||||
|
||||
@ -11,6 +11,11 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { AdminService, CouponInput } from './admin.service';
|
||||
import { ProductDto } from './dto/product.dto';
|
||||
import {
|
||||
UpdatePricingSettingsDto,
|
||||
ApplyGlobalMarginsDto,
|
||||
BulkPriceAdjustmentDto,
|
||||
} from './dto/pricing.dto';
|
||||
import { AdminQueryDto } from './dto/admin-query.dto';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import {
|
||||
@ -147,6 +152,33 @@ export class AdminController {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@Get('pricing/settings')
|
||||
@ApiOperation({ summary: 'دریافت تنظیمات هوشمند قیمتگذاری و گرد کردن' })
|
||||
async getPricingSettings() {
|
||||
const data = await this.adminService.getPricingSettings();
|
||||
return { success: true, data };
|
||||
}
|
||||
|
||||
@Put('pricing/settings')
|
||||
@ApiOperation({ summary: 'بروزرسانی تنظیمات هوشمند قیمتگذاری و گرد کردن' })
|
||||
async updatePricingSettings(@Body() dto: UpdatePricingSettingsDto) {
|
||||
return this.adminService.updatePricingSettings(dto);
|
||||
}
|
||||
|
||||
@Post('pricing/apply-global-margins')
|
||||
@ApiOperation({ summary: 'اعمال سراسری درصد سود تک و عمده بر روی محصولات' })
|
||||
async applyGlobalMargins(@Body() dto: ApplyGlobalMarginsDto) {
|
||||
return this.adminService.applyGlobalMargins(dto);
|
||||
}
|
||||
|
||||
@Post('pricing/bulk-adjust')
|
||||
@ApiOperation({
|
||||
summary: 'تغییرات گروهی قیمت (درصدی یا مبلغ ثابت) بر روی محصولات',
|
||||
})
|
||||
async bulkPriceAdjustment(@Body() dto: BulkPriceAdjustmentDto) {
|
||||
return this.adminService.bulkPriceAdjustment(dto);
|
||||
}
|
||||
|
||||
@Get('orders')
|
||||
@ApiOperation({ summary: 'لیست سفارشها (مدیریت)' })
|
||||
@ApiQuery({ name: 'page', required: false, description: 'شماره صفحه' })
|
||||
|
||||
@ -10,6 +10,18 @@ import { RedisService } from '../redis/redis.service';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import { CreateUserDto, UpdateUserDto } from './dto/user.dto';
|
||||
import { ProductDto } from './dto/product.dto';
|
||||
import {
|
||||
UpdatePricingSettingsDto,
|
||||
ApplyGlobalMarginsDto,
|
||||
BulkPriceAdjustmentDto,
|
||||
} from './dto/pricing.dto';
|
||||
import {
|
||||
DEFAULT_PRICING_SETTINGS,
|
||||
PricingSettings,
|
||||
applyRounding,
|
||||
calculateSellingPrice,
|
||||
RoundingMode,
|
||||
} from '../common/utils/pricing.utils';
|
||||
import { normalizeMobile } from '../common/utils/phone.utils';
|
||||
import { CANONICAL_ALIASES } from '../settings/settings.service';
|
||||
import { RevalidationService } from '../common/revalidation/revalidation.service';
|
||||
@ -424,6 +436,11 @@ export class AdminService {
|
||||
}
|
||||
|
||||
async createProduct(data: ProductDto) {
|
||||
const marginRetail =
|
||||
data.marginRetailPercent ?? data.priceValueMarginPercent;
|
||||
const marginWholesale =
|
||||
data.marginWholesalePercent ?? data.wholesaleMarginPercent;
|
||||
|
||||
const product = await this.prisma.product.create({
|
||||
data: {
|
||||
artNo: data.artNo || `ART-${Date.now()}`,
|
||||
@ -435,6 +452,10 @@ export class AdminService {
|
||||
categoryId: data.categoryId || '',
|
||||
categorySlug: data.categorySlug || 'general',
|
||||
buyPrice: data.buyPrice !== undefined ? data.buyPrice : 0,
|
||||
marginRetailPercent:
|
||||
marginRetail !== undefined ? marginRetail : undefined,
|
||||
marginWholesalePercent:
|
||||
marginWholesale !== undefined ? marginWholesale : undefined,
|
||||
priceValue: data.priceValue || 0,
|
||||
wholesalePrice:
|
||||
data.wholesalePrice !== undefined ? data.wholesalePrice : null,
|
||||
@ -498,6 +519,11 @@ export class AdminService {
|
||||
throw new NotFoundException(`محصولی با شناسه ${id} یافت نشد.`);
|
||||
}
|
||||
|
||||
const marginRetail =
|
||||
data.marginRetailPercent ?? data.priceValueMarginPercent;
|
||||
const marginWholesale =
|
||||
data.marginWholesalePercent ?? data.wholesaleMarginPercent;
|
||||
|
||||
const product = await this.prisma.product.update({
|
||||
where: { id },
|
||||
data: {
|
||||
@ -510,6 +536,10 @@ export class AdminService {
|
||||
categoryId: data.categoryId,
|
||||
categorySlug: data.categorySlug,
|
||||
buyPrice: data.buyPrice !== undefined ? data.buyPrice : undefined,
|
||||
marginRetailPercent:
|
||||
marginRetail !== undefined ? marginRetail : undefined,
|
||||
marginWholesalePercent:
|
||||
marginWholesale !== undefined ? marginWholesale : undefined,
|
||||
priceValue: data.priceValue,
|
||||
wholesalePrice:
|
||||
data.wholesalePrice !== undefined ? data.wholesalePrice : undefined,
|
||||
@ -1027,4 +1057,279 @@ export class AdminService {
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
async getPricingSettings(): Promise<PricingSettings> {
|
||||
const setting = await this.prisma.setting.findUnique({
|
||||
where: { key: 'pricing_config' },
|
||||
});
|
||||
if (!setting || !setting.value || typeof setting.value !== 'object') {
|
||||
return DEFAULT_PRICING_SETTINGS;
|
||||
}
|
||||
const val = setting.value as Record<string, any>;
|
||||
return {
|
||||
roundingStep: Number(
|
||||
val.roundingStep ?? DEFAULT_PRICING_SETTINGS.roundingStep,
|
||||
),
|
||||
roundingMode: (val.roundingMode ||
|
||||
DEFAULT_PRICING_SETTINGS.roundingMode) as RoundingMode,
|
||||
defaultRetailMarginPercent: Number(
|
||||
val.defaultRetailMarginPercent ??
|
||||
DEFAULT_PRICING_SETTINGS.defaultRetailMarginPercent,
|
||||
),
|
||||
defaultWholesaleMarginPercent: Number(
|
||||
val.defaultWholesaleMarginPercent ??
|
||||
DEFAULT_PRICING_SETTINGS.defaultWholesaleMarginPercent,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
async updatePricingSettings(dto: UpdatePricingSettingsDto) {
|
||||
const value = {
|
||||
roundingStep: Number(dto.roundingStep ?? 5000),
|
||||
roundingMode: dto.roundingMode || 'UP',
|
||||
defaultRetailMarginPercent: Number(dto.defaultRetailMarginPercent ?? 30),
|
||||
defaultWholesaleMarginPercent: Number(
|
||||
dto.defaultWholesaleMarginPercent ?? 15,
|
||||
),
|
||||
};
|
||||
|
||||
await this.prisma.setting.upsert({
|
||||
where: { key: 'pricing_config' },
|
||||
update: { category: 'pricing', value },
|
||||
create: { key: 'pricing_config', category: 'pricing', value },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'تنظیمات قیمتگذاری و گرد کردن با موفقیت ذخیره شد.',
|
||||
data: value,
|
||||
};
|
||||
}
|
||||
|
||||
async applyGlobalMargins(dto: ApplyGlobalMarginsDto) {
|
||||
const pricingSettings = await this.getPricingSettings();
|
||||
const roundingStep =
|
||||
dto.roundingStep !== undefined
|
||||
? Number(dto.roundingStep)
|
||||
: pricingSettings.roundingStep;
|
||||
const roundingMode = (dto.roundingMode ||
|
||||
pricingSettings.roundingMode) as RoundingMode;
|
||||
const retailMargin = Number(dto.retailMarginPercent);
|
||||
const wholesaleMargin = Number(dto.wholesaleMarginPercent);
|
||||
|
||||
const whereCondition: Prisma.ProductWhereInput = {};
|
||||
if (
|
||||
dto.scope === 'CATEGORY' &&
|
||||
dto.categoryIds &&
|
||||
dto.categoryIds.length > 0
|
||||
) {
|
||||
whereCondition.categoryId = { in: dto.categoryIds };
|
||||
} else if (
|
||||
dto.scope === 'SPECIFIC' &&
|
||||
dto.productIds &&
|
||||
dto.productIds.length > 0
|
||||
) {
|
||||
whereCondition.id = { in: dto.productIds };
|
||||
}
|
||||
|
||||
const products = await this.prisma.product.findMany({
|
||||
where: whereCondition,
|
||||
select: {
|
||||
id: true,
|
||||
slug: true,
|
||||
artNo: true,
|
||||
buyPrice: true,
|
||||
priceValue: true,
|
||||
wholesalePrice: true,
|
||||
},
|
||||
});
|
||||
|
||||
let updatedCount = 0;
|
||||
const revalidateSlugs: string[] = [];
|
||||
|
||||
for (const prod of products) {
|
||||
const buyPrice = Number(prod.buyPrice || 0);
|
||||
if (buyPrice > 0) {
|
||||
const newRetailPrice = calculateSellingPrice(
|
||||
buyPrice,
|
||||
retailMargin,
|
||||
roundingStep,
|
||||
roundingMode,
|
||||
);
|
||||
const newWholesalePrice = calculateSellingPrice(
|
||||
buyPrice,
|
||||
wholesaleMargin,
|
||||
roundingStep,
|
||||
roundingMode,
|
||||
);
|
||||
|
||||
await this.prisma.product.update({
|
||||
where: { id: prod.id },
|
||||
data: {
|
||||
priceValue: newRetailPrice,
|
||||
wholesalePrice: newWholesalePrice,
|
||||
marginRetailPercent: retailMargin,
|
||||
marginWholesalePercent: wholesaleMargin,
|
||||
},
|
||||
});
|
||||
updatedCount++;
|
||||
if (prod.slug) revalidateSlugs.push(prod.slug);
|
||||
}
|
||||
}
|
||||
|
||||
if (revalidateSlugs.length > 0) {
|
||||
for (const slug of revalidateSlugs.slice(0, 20)) {
|
||||
this.revalidationService.revalidateProduct(slug).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `درصد سود با موفقیت بر روی ${updatedCount} محصول اعمال و قیمتها گرد شدند.`,
|
||||
updatedCount,
|
||||
totalMatched: products.length,
|
||||
};
|
||||
}
|
||||
|
||||
async bulkPriceAdjustment(dto: BulkPriceAdjustmentDto) {
|
||||
const pricingSettings = await this.getPricingSettings();
|
||||
const roundingStep =
|
||||
dto.roundingStep !== undefined
|
||||
? Number(dto.roundingStep)
|
||||
: pricingSettings.roundingStep;
|
||||
const roundingMode = (dto.roundingMode ||
|
||||
pricingSettings.roundingMode) as RoundingMode;
|
||||
const applyRound = dto.applyRounding !== false;
|
||||
const adjValue = Number(dto.value || 0);
|
||||
|
||||
const whereCondition: Prisma.ProductWhereInput = {};
|
||||
if (
|
||||
dto.scope === 'CATEGORY' &&
|
||||
dto.categoryIds &&
|
||||
dto.categoryIds.length > 0
|
||||
) {
|
||||
whereCondition.categoryId = { in: dto.categoryIds };
|
||||
} else if (
|
||||
dto.scope === 'SPECIFIC' &&
|
||||
dto.productIds &&
|
||||
dto.productIds.length > 0
|
||||
) {
|
||||
whereCondition.id = { in: dto.productIds };
|
||||
}
|
||||
|
||||
const products = await this.prisma.product.findMany({
|
||||
where: whereCondition,
|
||||
select: {
|
||||
id: true,
|
||||
slug: true,
|
||||
artNo: true,
|
||||
buyPrice: true,
|
||||
priceValue: true,
|
||||
wholesalePrice: true,
|
||||
},
|
||||
});
|
||||
|
||||
let updatedCount = 0;
|
||||
const revalidateSlugs: string[] = [];
|
||||
|
||||
const computeNewPrice = (currentPrice: number): number => {
|
||||
let result = currentPrice;
|
||||
if (dto.adjustmentType === 'PERCENT') {
|
||||
const factor =
|
||||
dto.adjustmentDirection === 'INCREASE'
|
||||
? 1 + adjValue / 100
|
||||
: 1 - adjValue / 100;
|
||||
result = currentPrice * Math.max(0, factor);
|
||||
} else {
|
||||
const delta =
|
||||
dto.adjustmentDirection === 'INCREASE' ? adjValue : -adjValue;
|
||||
result = Math.max(0, currentPrice + delta);
|
||||
}
|
||||
return applyRound
|
||||
? applyRounding(result, roundingStep, roundingMode)
|
||||
: Math.round(result);
|
||||
};
|
||||
|
||||
for (const prod of products) {
|
||||
const updateData: Prisma.ProductUpdateInput = {};
|
||||
let changed = false;
|
||||
|
||||
const currentBuyPrice = Number(prod.buyPrice || 0);
|
||||
const currentRetailPrice = Number(prod.priceValue || 0);
|
||||
const currentWholesalePrice = Number(prod.wholesalePrice || 0);
|
||||
|
||||
let newBuyPrice = currentBuyPrice;
|
||||
let newRetailPrice = currentRetailPrice;
|
||||
let newWholesalePrice = currentWholesalePrice;
|
||||
|
||||
if (dto.targetField === 'BUY' || dto.targetField === 'ALL') {
|
||||
if (currentBuyPrice > 0) {
|
||||
newBuyPrice = computeNewPrice(currentBuyPrice);
|
||||
updateData.buyPrice = newBuyPrice;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
dto.targetField === 'RETAIL' ||
|
||||
dto.targetField === 'BOTH_SELLING' ||
|
||||
dto.targetField === 'ALL'
|
||||
) {
|
||||
if (currentRetailPrice > 0) {
|
||||
newRetailPrice = computeNewPrice(currentRetailPrice);
|
||||
updateData.priceValue = newRetailPrice;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
dto.targetField === 'WHOLESALE' ||
|
||||
dto.targetField === 'BOTH_SELLING' ||
|
||||
dto.targetField === 'ALL'
|
||||
) {
|
||||
if (currentWholesalePrice > 0) {
|
||||
newWholesalePrice = computeNewPrice(currentWholesalePrice);
|
||||
updateData.wholesalePrice = newWholesalePrice;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
if (newBuyPrice > 0) {
|
||||
if (newRetailPrice > 0) {
|
||||
updateData.marginRetailPercent =
|
||||
Math.round(
|
||||
((newRetailPrice - newBuyPrice) / newBuyPrice) * 10000,
|
||||
) / 100;
|
||||
}
|
||||
if (newWholesalePrice > 0) {
|
||||
updateData.marginWholesalePercent =
|
||||
Math.round(
|
||||
((newWholesalePrice - newBuyPrice) / newBuyPrice) * 10000,
|
||||
) / 100;
|
||||
}
|
||||
}
|
||||
|
||||
await this.prisma.product.update({
|
||||
where: { id: prod.id },
|
||||
data: updateData,
|
||||
});
|
||||
updatedCount++;
|
||||
if (prod.slug) revalidateSlugs.push(prod.slug);
|
||||
}
|
||||
}
|
||||
|
||||
if (revalidateSlugs.length > 0) {
|
||||
for (const slug of revalidateSlugs.slice(0, 20)) {
|
||||
this.revalidationService.revalidateProduct(slug).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `تغییرات قیمت با موفقیت بر روی ${updatedCount} محصول اعمال شد.`,
|
||||
updatedCount,
|
||||
totalMatched: products.length,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
158
backend/src/admin/dto/pricing.dto.ts
Normal file
158
backend/src/admin/dto/pricing.dto.ts
Normal file
@ -0,0 +1,158 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import {
|
||||
IsNumber,
|
||||
IsString,
|
||||
IsIn,
|
||||
IsOptional,
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
|
||||
export class UpdatePricingSettingsDto {
|
||||
@ApiProperty({ description: 'گام گرد کردن قیمت به تومان', example: 5000 })
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
roundingStep!: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'جهت گرد کردن',
|
||||
enum: ['UP', 'DOWN', 'NEAREST'],
|
||||
example: 'UP',
|
||||
})
|
||||
@IsString()
|
||||
@IsIn(['UP', 'DOWN', 'NEAREST'])
|
||||
roundingMode!: 'UP' | 'DOWN' | 'NEAREST';
|
||||
|
||||
@ApiProperty({ description: 'درصد سود پیشفرض تکفروشی', example: 30 })
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
defaultRetailMarginPercent!: number;
|
||||
|
||||
@ApiProperty({ description: 'درصد سود پیشفرض عمدهفروشی', example: 15 })
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
defaultWholesaleMarginPercent!: number;
|
||||
}
|
||||
|
||||
export class ApplyGlobalMarginsDto {
|
||||
@ApiProperty({ description: 'درصد سود تکفروشی برای اعمال سراسری', example: 30 })
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
retailMarginPercent!: number;
|
||||
|
||||
@ApiProperty({ description: 'درصد سود عمدهفروشی برای اعمال سراسری', example: 15 })
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
wholesaleMarginPercent!: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'گام گرد کردن سفارشی (اختیاری)' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
roundingStep?: number;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'جهت گرد کردن سفارشی',
|
||||
enum: ['UP', 'DOWN', 'NEAREST'],
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsIn(['UP', 'DOWN', 'NEAREST'])
|
||||
roundingMode?: 'UP' | 'DOWN' | 'NEAREST';
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'دامنه محصولات',
|
||||
enum: ['ALL', 'CATEGORY', 'SPECIFIC'],
|
||||
default: 'ALL',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsIn(['ALL', 'CATEGORY', 'SPECIFIC'])
|
||||
scope?: 'ALL' | 'CATEGORY' | 'SPECIFIC';
|
||||
|
||||
@ApiPropertyOptional({ description: 'شناسه دستهبندیها در صورت انتخاب دامنه CATEGORY' })
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
categoryIds?: string[];
|
||||
|
||||
@ApiPropertyOptional({ description: 'شناسه محصولات در صورت انتخاب دامنه SPECIFIC' })
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
productIds?: string[];
|
||||
}
|
||||
|
||||
export class BulkPriceAdjustmentDto {
|
||||
@ApiProperty({
|
||||
description: 'دامنه محصولات',
|
||||
enum: ['ALL', 'CATEGORY', 'SPECIFIC'],
|
||||
example: 'ALL',
|
||||
})
|
||||
@IsString()
|
||||
@IsIn(['ALL', 'CATEGORY', 'SPECIFIC'])
|
||||
scope!: 'ALL' | 'CATEGORY' | 'SPECIFIC';
|
||||
|
||||
@ApiPropertyOptional({ description: 'شناسه دستهبندیها در صورت انتخاب دامنه CATEGORY' })
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
categoryIds?: string[];
|
||||
|
||||
@ApiPropertyOptional({ description: 'شناسه محصولات در صورت انتخاب دامنه SPECIFIC' })
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
productIds?: string[];
|
||||
|
||||
@ApiProperty({
|
||||
description: 'فیلد هدف برای تغییر قیمت',
|
||||
enum: ['RETAIL', 'WHOLESALE', 'BUY', 'BOTH_SELLING', 'ALL'],
|
||||
example: 'RETAIL',
|
||||
})
|
||||
@IsString()
|
||||
@IsIn(['RETAIL', 'WHOLESALE', 'BUY', 'BOTH_SELLING', 'ALL'])
|
||||
targetField!: 'RETAIL' | 'WHOLESALE' | 'BUY' | 'BOTH_SELLING' | 'ALL';
|
||||
|
||||
@ApiProperty({
|
||||
description: 'نوع تغییر: PERCENT (درصدی) یا FIXED (مبلغ ثابت)',
|
||||
enum: ['PERCENT', 'FIXED'],
|
||||
example: 'PERCENT',
|
||||
})
|
||||
@IsString()
|
||||
@IsIn(['PERCENT', 'FIXED'])
|
||||
adjustmentType!: 'PERCENT' | 'FIXED';
|
||||
|
||||
@ApiProperty({
|
||||
description: 'جهت تغییر: INCREASE (افزایش / گرانتر) یا DECREASE (کاهش / ارزانتر)',
|
||||
enum: ['INCREASE', 'DECREASE'],
|
||||
example: 'INCREASE',
|
||||
})
|
||||
@IsString()
|
||||
@IsIn(['INCREASE', 'DECREASE'])
|
||||
adjustmentDirection!: 'INCREASE' | 'DECREASE';
|
||||
|
||||
@ApiProperty({
|
||||
description: 'مقدار تغییر (مثلاً 10 برای 10 درصد، یا 100000 برای 100 هزار تومان)',
|
||||
example: 10,
|
||||
})
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
value!: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'آیا پس از تغییر قیمت، گرد کردن اعمال شود؟', default: true })
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
applyRounding?: boolean;
|
||||
|
||||
@ApiPropertyOptional({ description: 'گام گرد کردن سفارشی (در صورت خالی بودن از تنظیمات سیستم استفاده میشود)' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
roundingStep?: number;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'جهت گرد کردن سفارشی',
|
||||
enum: ['UP', 'DOWN', 'NEAREST'],
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsIn(['UP', 'DOWN', 'NEAREST'])
|
||||
roundingMode?: 'UP' | 'DOWN' | 'NEAREST';
|
||||
}
|
||||
@ -66,11 +66,21 @@ export class ProductDto {
|
||||
@ApiPropertyOptional({ description: 'درصد سود قیمت مصرفکننده' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
marginRetailPercent?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'درصد سود قیمت مصرفکننده (نام دیگر)' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
priceValueMarginPercent?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'درصد سود قیمت عمدهفروشی' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
marginWholesalePercent?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'درصد سود قیمت عمدهفروشی (نام دیگر)' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
wholesaleMarginPercent?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'نمایش متنی قیمت' })
|
||||
|
||||
52
backend/src/common/utils/pricing.utils.ts
Normal file
52
backend/src/common/utils/pricing.utils.ts
Normal file
@ -0,0 +1,52 @@
|
||||
export type RoundingMode = 'UP' | 'DOWN' | 'NEAREST';
|
||||
|
||||
export interface PricingSettings {
|
||||
roundingStep: number;
|
||||
roundingMode: RoundingMode;
|
||||
defaultRetailMarginPercent: number;
|
||||
defaultWholesaleMarginPercent: number;
|
||||
}
|
||||
|
||||
export const DEFAULT_PRICING_SETTINGS: PricingSettings = {
|
||||
roundingStep: 5000,
|
||||
roundingMode: 'UP',
|
||||
defaultRetailMarginPercent: 30,
|
||||
defaultWholesaleMarginPercent: 15,
|
||||
};
|
||||
|
||||
/**
|
||||
* Rounds a given numeric price according to the configured step and direction.
|
||||
* @param price Raw price in Tomans
|
||||
* @param step Rounding step (e.g. 1000, 5000, 10000, 50000)
|
||||
* @param mode Direction: 'UP' (Ceil), 'DOWN' (Floor), 'NEAREST' (Round)
|
||||
*/
|
||||
export function applyRounding(
|
||||
price: number,
|
||||
step: number = 5000,
|
||||
mode: RoundingMode = 'UP',
|
||||
): number {
|
||||
if (isNaN(price) || price <= 0) return 0;
|
||||
if (!step || step <= 1) return Math.round(price);
|
||||
|
||||
if (mode === 'UP') {
|
||||
return Math.ceil(price / step) * step;
|
||||
} else if (mode === 'DOWN') {
|
||||
return Math.floor(price / step) * step;
|
||||
} else {
|
||||
return Math.round(price / step) * step;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates selling price from buy price and profit margin, with smart rounding applied.
|
||||
*/
|
||||
export function calculateSellingPrice(
|
||||
buyPrice: number,
|
||||
marginPercent: number,
|
||||
roundingStep: number = 5000,
|
||||
roundingMode: RoundingMode = 'UP',
|
||||
): number {
|
||||
if (isNaN(buyPrice) || buyPrice <= 0) return 0;
|
||||
const rawPrice = buyPrice * (1 + (marginPercent || 0) / 100);
|
||||
return applyRounding(rawPrice, roundingStep, roundingMode);
|
||||
}
|
||||
@ -1,12 +1,12 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { DollarSign, Save, Plus, X, Heart, Percent, Wallet } from 'lucide-react';
|
||||
import { DollarSign, Save, Plus, X, Heart, Percent, Wallet, Calculator, ArrowUpRight, ArrowDownRight, Compass } from 'lucide-react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import api from '../services/api';
|
||||
import Spinner from '../components/ui/Spinner';
|
||||
import PriceInput from '../components/ui/PriceInput';
|
||||
import Button from '../components/ui/Button';
|
||||
import type { FinancialSettings } from '../types/admin';
|
||||
|
||||
import type { FinancialSettings, PricingSettings } from '../types/admin';
|
||||
import { calculateSellingPrice } from '../utils/pricing';
|
||||
|
||||
export default function FinancialSettingsPage() {
|
||||
const [financial, setFinancial] = useState<FinancialSettings>({
|
||||
@ -19,6 +19,16 @@ export default function FinancialSettingsPage() {
|
||||
const [charityStep, setCharityStep] = useState('10000');
|
||||
const [refillEnabled, setRefillEnabled] = useState(false);
|
||||
const [refillPercent, setRefillPercent] = useState('5');
|
||||
|
||||
// Smart Pricing & Rounding Settings
|
||||
const [pricing, setPricing] = useState<PricingSettings>({
|
||||
roundingStep: 5000,
|
||||
roundingMode: 'UP',
|
||||
defaultRetailMarginPercent: 30,
|
||||
defaultWholesaleMarginPercent: 15,
|
||||
});
|
||||
const [testBuyPrice, setTestBuyPrice] = useState('1000000');
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [donationInput, setDonationInput] = useState('');
|
||||
@ -28,8 +38,9 @@ export default function FinancialSettingsPage() {
|
||||
Promise.all([
|
||||
api.get('/settings/financial'),
|
||||
api.get('/admin/settings'),
|
||||
api.get('/admin/pricing/settings'),
|
||||
])
|
||||
.then(([resFin, resAdmin]) => {
|
||||
.then(([resFin, resAdmin, resPricing]) => {
|
||||
if (!isSubscribed) return;
|
||||
const data = resFin.data?.data || resFin.data;
|
||||
if (data) {
|
||||
@ -48,6 +59,15 @@ export default function FinancialSettingsPage() {
|
||||
setRefillEnabled(resAdmin.data.data.REFILL_SUBSCRIPTION_ENABLED === 'true');
|
||||
setRefillPercent(resAdmin.data.data.REFILL_REWARD_PERCENT || '5');
|
||||
}
|
||||
if (resPricing.data?.success && resPricing.data.data) {
|
||||
const p = resPricing.data.data;
|
||||
setPricing({
|
||||
roundingStep: Number(p.roundingStep || 5000),
|
||||
roundingMode: p.roundingMode || 'UP',
|
||||
defaultRetailMarginPercent: Number(p.defaultRetailMarginPercent || 30),
|
||||
defaultWholesaleMarginPercent: Number(p.defaultWholesaleMarginPercent || 15),
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn('Failed to fetch Financial settings', err);
|
||||
@ -247,6 +267,186 @@ export default function FinancialSettingsPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Smart Pricing & Rounding Rules Section */}
|
||||
<div className="md:col-span-2 pt-6 border-t-2 border-dashed border-gray-200 space-y-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-base font-black text-gray-900 flex items-center gap-2">
|
||||
<Calculator className="w-5 h-5 text-indigo-600" />
|
||||
تنظیمات هوشمند فرمول سود و گرد کردن قیمتها (Pricing & Rounding Rules)
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
تعیین گام و جهت رند شدن قیمتها و درصد سود پیشفرض برای فروش خرد و عمده. این تنظیمات در فرم ویرایش محصول و تغییرات گروهی قیمت اعمال میگردد.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-br from-indigo-50/60 via-purple-50/30 to-blue-50/50 p-5 rounded-2xl border border-indigo-100/80 space-y-5">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
{/* Rounding Step */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-gray-800 mb-1.5 flex items-center gap-1.5">
|
||||
<Compass className="w-4 h-4 text-indigo-600" />
|
||||
<span>گام گرد کردن قیمت (پله رند شدن به تومان)</span>
|
||||
</label>
|
||||
<PriceInput
|
||||
value={String(pricing.roundingStep)}
|
||||
onChange={(val) => setPricing({ ...pricing, roundingStep: Math.max(0, Number(val) || 0) })}
|
||||
placeholder="5000"
|
||||
className="px-4 py-2.5 bg-white rounded-xl border border-indigo-200 focus:border-indigo-600 text-sm font-bold"
|
||||
/>
|
||||
<div className="flex flex-wrap gap-1.5 mt-2">
|
||||
{[1000, 5000, 10000, 50000, 100000].map((stepVal) => (
|
||||
<button
|
||||
key={stepVal}
|
||||
type="button"
|
||||
onClick={() => setPricing({ ...pricing, roundingStep: stepVal })}
|
||||
className={`px-2.5 py-1 text-xs font-bold rounded-lg border transition-all cursor-pointer ${
|
||||
pricing.roundingStep === stepVal
|
||||
? 'bg-indigo-600 text-white border-indigo-600 shadow-sm'
|
||||
: 'bg-white text-gray-600 border-gray-200 hover:border-indigo-300'
|
||||
}`}
|
||||
>
|
||||
{stepVal.toLocaleString('fa-IR')} تومان
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Rounding Mode / Direction */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-gray-800 mb-1.5 flex items-center gap-1.5">
|
||||
<span>جهت گرد کردن قیمت (Rounding Direction)</span>
|
||||
</label>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPricing({ ...pricing, roundingMode: 'UP' })}
|
||||
className={`p-3 rounded-xl border text-center transition-all cursor-pointer ${
|
||||
pricing.roundingMode === 'UP'
|
||||
? 'bg-indigo-600 text-white border-indigo-600 shadow-md ring-2 ring-indigo-300'
|
||||
: 'bg-white text-gray-700 border-gray-200 hover:border-indigo-300'
|
||||
}`}
|
||||
>
|
||||
<ArrowUpRight className={`w-4 h-4 mx-auto mb-1 ${pricing.roundingMode === 'UP' ? 'text-white' : 'text-emerald-600'}`} />
|
||||
<span className="block text-xs font-black">به سمت بالا</span>
|
||||
<span className="block text-[10px] opacity-80 mt-0.5">سقف (Ceil)</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPricing({ ...pricing, roundingMode: 'DOWN' })}
|
||||
className={`p-3 rounded-xl border text-center transition-all cursor-pointer ${
|
||||
pricing.roundingMode === 'DOWN'
|
||||
? 'bg-indigo-600 text-white border-indigo-600 shadow-md ring-2 ring-indigo-300'
|
||||
: 'bg-white text-gray-700 border-gray-200 hover:border-indigo-300'
|
||||
}`}
|
||||
>
|
||||
<ArrowDownRight className={`w-4 h-4 mx-auto mb-1 ${pricing.roundingMode === 'DOWN' ? 'text-white' : 'text-rose-600'}`} />
|
||||
<span className="block text-xs font-black">به سمت پایین</span>
|
||||
<span className="block text-[10px] opacity-80 mt-0.5">کف (Floor)</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPricing({ ...pricing, roundingMode: 'NEAREST' })}
|
||||
className={`p-3 rounded-xl border text-center transition-all cursor-pointer ${
|
||||
pricing.roundingMode === 'NEAREST'
|
||||
? 'bg-indigo-600 text-white border-indigo-600 shadow-md ring-2 ring-indigo-300'
|
||||
: 'bg-white text-gray-700 border-gray-200 hover:border-indigo-300'
|
||||
}`}
|
||||
>
|
||||
<Compass className={`w-4 h-4 mx-auto mb-1 ${pricing.roundingMode === 'NEAREST' ? 'text-white' : 'text-indigo-600'}`} />
|
||||
<span className="block text-xs font-black">نزدیکترین</span>
|
||||
<span className="block text-[10px] opacity-80 mt-0.5">ریاضی (Round)</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Default Retail Margin */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-gray-800 mb-1.5">
|
||||
درصد سود پیشفرض تکفروشی (Retail Margin %)
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="500"
|
||||
step="0.5"
|
||||
value={pricing.defaultRetailMarginPercent}
|
||||
onChange={(e) => setPricing({ ...pricing, defaultRetailMarginPercent: Number(e.target.value) || 0 })}
|
||||
className="w-full px-4 py-2.5 bg-white rounded-xl border border-indigo-200 focus:border-indigo-600 outline-none font-bold text-sm"
|
||||
dir="ltr"
|
||||
/>
|
||||
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 font-bold text-xs">%</span>
|
||||
</div>
|
||||
<p className="text-[11px] text-gray-500 mt-1">فرمول: قیمت خرید × (۱ + سود٪)</p>
|
||||
</div>
|
||||
|
||||
{/* Default Wholesale Margin */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-gray-800 mb-1.5">
|
||||
درصد سود پیشفرض عمدهفروشی (Wholesale Margin %)
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="500"
|
||||
step="0.5"
|
||||
value={pricing.defaultWholesaleMarginPercent}
|
||||
onChange={(e) => setPricing({ ...pricing, defaultWholesaleMarginPercent: Number(e.target.value) || 0 })}
|
||||
className="w-full px-4 py-2.5 bg-white rounded-xl border border-indigo-200 focus:border-indigo-600 outline-none font-bold text-sm"
|
||||
dir="ltr"
|
||||
/>
|
||||
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 font-bold text-xs">%</span>
|
||||
</div>
|
||||
<p className="text-[11px] text-gray-500 mt-1">برای مشتریان همکار و داروخانههای دامپزشکی B2B</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Live Simulation / Test Box */}
|
||||
<div className="bg-white/90 p-4 rounded-xl border border-indigo-100 shadow-sm space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs font-black text-indigo-900 flex items-center gap-1.5">
|
||||
<Calculator className="w-4 h-4 text-indigo-600" />
|
||||
پیشنمایش زنده خروجی با تنظیمات فعلی:
|
||||
</span>
|
||||
<span className="text-[11px] text-indigo-600 font-medium">
|
||||
گام: {pricing.roundingStep.toLocaleString('fa-IR')} تومان ({pricing.roundingMode === 'UP' ? 'گرد به بالا' : pricing.roundingMode === 'DOWN' ? 'گرد به پایین' : 'گرد به نزدیکترین'})
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 items-center">
|
||||
<div>
|
||||
<span className="block text-[11px] text-gray-500 mb-1 font-bold">قیمت خرید فرضی (تست):</span>
|
||||
<PriceInput
|
||||
value={testBuyPrice}
|
||||
onChange={(val) => setTestBuyPrice(val)}
|
||||
placeholder="1,000,000"
|
||||
className="px-3 py-2 bg-gray-50 rounded-lg border border-gray-200 text-xs font-bold"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-emerald-50/70 p-3 rounded-xl border border-emerald-200">
|
||||
<span className="block text-[10px] text-emerald-800 font-bold">قیمت تکفروشی محاسبهشده (+{pricing.defaultRetailMarginPercent}٪):</span>
|
||||
<span className="text-sm font-black text-emerald-700 block mt-0.5">
|
||||
{calculateSellingPrice(Number(testBuyPrice) || 0, pricing.defaultRetailMarginPercent, pricing.roundingStep, pricing.roundingMode).toLocaleString('fa-IR')} تومان
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="bg-blue-50/70 p-3 rounded-xl border border-blue-200">
|
||||
<span className="block text-[10px] text-blue-800 font-bold">قیمت عمدهفروشی محاسبهشده (+{pricing.defaultWholesaleMarginPercent}٪):</span>
|
||||
<span className="text-sm font-black text-blue-700 block mt-0.5">
|
||||
{calculateSellingPrice(Number(testBuyPrice) || 0, pricing.defaultWholesaleMarginPercent, pricing.roundingStep, pricing.roundingMode).toLocaleString('fa-IR')} تومان
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Wallet Withdrawal Feature Toggle */}
|
||||
<div className="md:col-span-2 pt-4 border-t border-gray-100">
|
||||
<label className="flex items-center gap-3 cursor-pointer p-4 bg-emerald-50/50 hover:bg-emerald-50 rounded-2xl border border-emerald-100 transition-colors">
|
||||
@ -277,7 +477,7 @@ export default function FinancialSettingsPage() {
|
||||
startIcon={Save}
|
||||
isLoading={isSaving}
|
||||
>
|
||||
ذخیره تنظیمات مالی
|
||||
ذخیره تنظیمات مالی و قیمتگذاری
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -1,6 +1,34 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { Search, Filter, Box, Plus, Edit2, Trash2, Image as ImageIcon, X, HelpCircle, ArrowUpDown, ArrowUp, ArrowDown, GripVertical, Film, Play, ChevronRight, ChevronLeft, Link2 } from 'lucide-react';
|
||||
import {
|
||||
Search,
|
||||
Filter,
|
||||
Box,
|
||||
Plus,
|
||||
Edit2,
|
||||
Trash2,
|
||||
Image as ImageIcon,
|
||||
X,
|
||||
HelpCircle,
|
||||
ArrowUpDown,
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
GripVertical,
|
||||
Film,
|
||||
Play,
|
||||
ChevronRight,
|
||||
ChevronLeft,
|
||||
Link2,
|
||||
Calculator,
|
||||
TrendingUp,
|
||||
TrendingDown,
|
||||
DollarSign,
|
||||
Percent,
|
||||
Layers,
|
||||
RotateCcw,
|
||||
CheckCircle2,
|
||||
Compass,
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import api, { BASE_DOMAIN } from '../services/api';
|
||||
import Spinner from '../components/ui/Spinner';
|
||||
@ -10,8 +38,12 @@ import ConfirmModal from '../components/ui/ConfirmModal';
|
||||
import PriceInput from '../components/ui/PriceInput';
|
||||
import Button from '../components/ui/Button';
|
||||
import RichTextEditor from '../components/ui/RichTextEditor';
|
||||
|
||||
|
||||
import type { PricingSettings } from '../types/admin';
|
||||
import {
|
||||
DEFAULT_PRICING_SETTINGS,
|
||||
calculateSellingPrice,
|
||||
applyRounding,
|
||||
} from '../utils/pricing';
|
||||
|
||||
export interface Category {
|
||||
id: string;
|
||||
@ -34,6 +66,8 @@ export interface Product {
|
||||
priceValue?: number;
|
||||
wholesalePrice?: number;
|
||||
buyPrice?: number;
|
||||
marginRetailPercent?: number;
|
||||
marginWholesalePercent?: number;
|
||||
priceDisplay?: string;
|
||||
unit?: string;
|
||||
packageSize?: number;
|
||||
@ -224,6 +258,33 @@ export default function Products() {
|
||||
preorderDeposit: '' as number | string
|
||||
});
|
||||
|
||||
// Smart Pricing & Rounding state
|
||||
const [pricingSettings, setPricingSettings] = useState<PricingSettings>(DEFAULT_PRICING_SETTINGS);
|
||||
|
||||
// Bulk Price Modal State
|
||||
const [isBulkPriceModalOpen, setIsBulkPriceModalOpen] = useState(false);
|
||||
const [bulkPriceTab, setBulkPriceTab] = useState<'adjust' | 'margin'>('adjust');
|
||||
const [isBulkApplying, setIsBulkApplying] = useState(false);
|
||||
|
||||
// Bulk Adjust form state
|
||||
const [bulkAdjustForm, setBulkAdjustForm] = useState({
|
||||
scope: 'ALL' as 'ALL' | 'CATEGORY',
|
||||
categoryIds: [] as string[],
|
||||
targetField: 'RETAIL' as 'RETAIL' | 'WHOLESALE' | 'BUY' | 'BOTH_SELLING' | 'ALL',
|
||||
adjustmentType: 'PERCENT' as 'PERCENT' | 'FIXED',
|
||||
adjustmentDirection: 'INCREASE' as 'INCREASE' | 'DECREASE',
|
||||
value: 10,
|
||||
applyRounding: true,
|
||||
});
|
||||
|
||||
// Global Margin form state
|
||||
const [globalMarginForm, setGlobalMarginForm] = useState({
|
||||
scope: 'ALL' as 'ALL' | 'CATEGORY',
|
||||
categoryIds: [] as string[],
|
||||
retailMarginPercent: 30,
|
||||
wholesaleMarginPercent: 15,
|
||||
});
|
||||
|
||||
const [draggedMediaIdx, setDraggedMediaIdx] = useState<number | null>(null);
|
||||
const [dragOverMediaIdx, setDragOverMediaIdx] = useState<number | null>(null);
|
||||
const [customMediaUrl, setCustomMediaUrl] = useState('');
|
||||
@ -313,12 +374,13 @@ export default function Products() {
|
||||
const pPrice = product.priceValue !== undefined && product.priceValue !== null ? Number(product.priceValue) : '';
|
||||
const wPrice = product.wholesalePrice !== undefined && product.wholesalePrice !== null ? Number(product.wholesalePrice) : '';
|
||||
|
||||
let pMargin: number | string = '';
|
||||
let wMargin: number | string = '';
|
||||
if (bPrice && pPrice !== '') {
|
||||
let pMargin: number | string = product.marginRetailPercent !== undefined && product.marginRetailPercent !== null ? Number(product.marginRetailPercent) : '';
|
||||
let wMargin: number | string = product.marginWholesalePercent !== undefined && product.marginWholesalePercent !== null ? Number(product.marginWholesalePercent) : '';
|
||||
|
||||
if (bPrice && pPrice !== '' && pMargin === '') {
|
||||
pMargin = Math.round(((Number(pPrice) - Number(bPrice)) / Number(bPrice)) * 100);
|
||||
}
|
||||
if (bPrice && wPrice !== '') {
|
||||
if (bPrice && wPrice !== '' && wMargin === '') {
|
||||
wMargin = Math.round(((Number(wPrice) - Number(bPrice)) / Number(bPrice)) * 100);
|
||||
}
|
||||
|
||||
@ -373,7 +435,9 @@ export default function Products() {
|
||||
updateUrlParams({ modal: 'create', productId: undefined, tab: activeTab || 'general' });
|
||||
setFormData({
|
||||
artNo: '', nameFa: '', nameEn: '', scientificTagline: '', description: '', shortDescription: '', categoryId: '',
|
||||
buyPrice: '', priceValue: '', wholesalePrice: '', priceValueMarginPercent: '', wholesaleMarginPercent: '',
|
||||
buyPrice: '', priceValue: '', wholesalePrice: '',
|
||||
priceValueMarginPercent: pricingSettings.defaultRetailMarginPercent,
|
||||
wholesaleMarginPercent: pricingSettings.defaultWholesaleMarginPercent,
|
||||
priceDisplay: '', unit: '', packageSize: '', dosageLogic: '', suitableFor: 'سگ و گربه',
|
||||
imageUrl: '', metaTitle: '', metaDescription: '', keywords: '', canonicalUrl: '', slug: '',
|
||||
stockStatus: 'IN_STOCK', noIndex: false, noFollow: false, ogImage: '', featuredImageAlt: '',
|
||||
@ -444,6 +508,27 @@ export default function Products() {
|
||||
} catch (err) {
|
||||
console.warn('Could not fetch categories', err);
|
||||
}
|
||||
|
||||
// Fetch pricing settings
|
||||
try {
|
||||
const pricingRes = await api.get('/admin/pricing/settings');
|
||||
if (pricingRes.data?.data) {
|
||||
const p = pricingRes.data.data;
|
||||
setPricingSettings({
|
||||
roundingStep: Number(p.roundingStep || 5000),
|
||||
roundingMode: p.roundingMode || 'UP',
|
||||
defaultRetailMarginPercent: Number(p.defaultRetailMarginPercent || 30),
|
||||
defaultWholesaleMarginPercent: Number(p.defaultWholesaleMarginPercent || 15),
|
||||
});
|
||||
setGlobalMarginForm(prev => ({
|
||||
...prev,
|
||||
retailMarginPercent: Number(p.defaultRetailMarginPercent || 30),
|
||||
wholesaleMarginPercent: Number(p.defaultWholesaleMarginPercent || 15),
|
||||
}));
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Could not fetch pricing settings', err);
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
console.error(err);
|
||||
toast.error('خطا در دریافت لیست محصولات');
|
||||
@ -459,6 +544,61 @@ export default function Products() {
|
||||
return () => clearTimeout(timer);
|
||||
}, [fetchData]);
|
||||
|
||||
// Handle Apply Global Margins
|
||||
const handleApplyGlobalMargins = async () => {
|
||||
try {
|
||||
setIsBulkApplying(true);
|
||||
const res = await api.post('/admin/pricing/apply-global-margins', {
|
||||
scope: globalMarginForm.scope,
|
||||
categoryIds: globalMarginForm.scope === 'CATEGORY' ? globalMarginForm.categoryIds : undefined,
|
||||
retailMarginPercent: Number(globalMarginForm.retailMarginPercent),
|
||||
wholesaleMarginPercent: Number(globalMarginForm.wholesaleMarginPercent),
|
||||
roundingStep: pricingSettings.roundingStep,
|
||||
roundingMode: pricingSettings.roundingMode,
|
||||
});
|
||||
const data = res.data?.data || res.data;
|
||||
toast.success(data?.message || 'درصد سود با موفقیت بر روی محصولات اعمال شد');
|
||||
setIsBulkPriceModalOpen(false);
|
||||
await fetchData();
|
||||
} catch (err: any) {
|
||||
console.error('Failed to apply global margins', err);
|
||||
toast.error(err.response?.data?.message || 'خطا در اعمال سراسری درصد سود');
|
||||
} finally {
|
||||
setIsBulkApplying(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle Bulk Price Adjustment (fixed / percentage)
|
||||
const handleBulkPriceAdjustment = async () => {
|
||||
if (!bulkAdjustForm.value || Number(bulkAdjustForm.value) <= 0) {
|
||||
toast.error('لطفاً مقدار تغییر قیمت را وارد نمایید');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setIsBulkApplying(true);
|
||||
const res = await api.post('/admin/pricing/bulk-adjust', {
|
||||
scope: bulkAdjustForm.scope,
|
||||
categoryIds: bulkAdjustForm.scope === 'CATEGORY' ? bulkAdjustForm.categoryIds : undefined,
|
||||
targetField: bulkAdjustForm.targetField,
|
||||
adjustmentType: bulkAdjustForm.adjustmentType,
|
||||
adjustmentDirection: bulkAdjustForm.adjustmentDirection,
|
||||
value: Number(bulkAdjustForm.value),
|
||||
applyRounding: bulkAdjustForm.applyRounding,
|
||||
roundingStep: pricingSettings.roundingStep,
|
||||
roundingMode: pricingSettings.roundingMode,
|
||||
});
|
||||
const data = res.data?.data || res.data;
|
||||
toast.success(data?.message || 'تغییرات گروهی قیمت با موفقیت اعمال شد');
|
||||
setIsBulkPriceModalOpen(false);
|
||||
await fetchData();
|
||||
} catch (err: any) {
|
||||
console.error('Failed to adjust bulk prices', err);
|
||||
toast.error(err.response?.data?.message || 'خطا در اعمال تغییرات گروهی قیمت');
|
||||
} finally {
|
||||
setIsBulkApplying(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
const handleSave = async (e: React.FormEvent) => {
|
||||
@ -593,14 +733,26 @@ export default function Products() {
|
||||
</h2>
|
||||
<p className="text-gray-500 font-medium mt-1">افزودن و ویرایش پیشرفته محصولات با تنظیمات سئو</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
startIcon={Plus}
|
||||
onClick={() => openModal()}
|
||||
>
|
||||
محصول جدید
|
||||
</Button>
|
||||
<div className="flex items-center gap-3">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
startIcon={Calculator}
|
||||
onClick={() => setIsBulkPriceModalOpen(true)}
|
||||
className="border-purple-200 text-purple-700 hover:bg-purple-50 font-bold"
|
||||
>
|
||||
مدیریت گروهی قیمتها
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
startIcon={Plus}
|
||||
onClick={() => openModal()}
|
||||
>
|
||||
محصول جدید
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1119,15 +1271,29 @@ export default function Products() {
|
||||
let wMargin = formData.wholesaleMarginPercent;
|
||||
|
||||
if (bPrice && pMargin !== '') {
|
||||
pPrice = Math.round(Number(bPrice) * (1 + Number(pMargin) / 100));
|
||||
pPrice = calculateSellingPrice(
|
||||
Number(bPrice),
|
||||
Number(pMargin),
|
||||
pricingSettings.roundingStep,
|
||||
pricingSettings.roundingMode,
|
||||
);
|
||||
} else if (bPrice && pPrice) {
|
||||
pMargin = Math.round(((Number(pPrice) - Number(bPrice)) / Number(bPrice)) * 100);
|
||||
pMargin = Math.round(
|
||||
((Number(pPrice) - Number(bPrice)) / Number(bPrice)) * 100,
|
||||
);
|
||||
}
|
||||
|
||||
if (bPrice && wMargin !== '') {
|
||||
wPrice = Math.round(Number(bPrice) * (1 + Number(wMargin) / 100));
|
||||
wPrice = calculateSellingPrice(
|
||||
Number(bPrice),
|
||||
Number(wMargin),
|
||||
pricingSettings.roundingStep,
|
||||
pricingSettings.roundingMode,
|
||||
);
|
||||
} else if (bPrice && wPrice) {
|
||||
wMargin = Math.round(((Number(wPrice) - Number(bPrice)) / Number(bPrice)) * 100);
|
||||
wMargin = Math.round(
|
||||
((Number(wPrice) - Number(bPrice)) / Number(bPrice)) * 100,
|
||||
);
|
||||
}
|
||||
|
||||
setFormData({
|
||||
@ -1179,7 +1345,12 @@ export default function Products() {
|
||||
const bPrice = formData.buyPrice ? Number(formData.buyPrice) : null;
|
||||
let pPrice = formData.priceValue;
|
||||
if (bPrice && margin !== '') {
|
||||
pPrice = Math.round(bPrice * (1 + Number(margin) / 100));
|
||||
pPrice = calculateSellingPrice(
|
||||
bPrice,
|
||||
Number(margin),
|
||||
pricingSettings.roundingStep,
|
||||
pricingSettings.roundingMode,
|
||||
);
|
||||
}
|
||||
setFormData({
|
||||
...formData,
|
||||
@ -1230,7 +1401,12 @@ export default function Products() {
|
||||
const bPrice = formData.buyPrice ? Number(formData.buyPrice) : null;
|
||||
let wPrice = formData.wholesalePrice;
|
||||
if (bPrice && margin !== '') {
|
||||
wPrice = Math.round(bPrice * (1 + Number(margin) / 100));
|
||||
wPrice = calculateSellingPrice(
|
||||
bPrice,
|
||||
Number(margin),
|
||||
pricingSettings.roundingStep,
|
||||
pricingSettings.roundingMode,
|
||||
);
|
||||
}
|
||||
setFormData({
|
||||
...formData,
|
||||
@ -1247,6 +1423,23 @@ export default function Products() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Active Rounding Info Badge */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 px-4 py-2.5 bg-indigo-50/70 border border-indigo-100 rounded-2xl text-xs text-indigo-900">
|
||||
<div className="flex items-center gap-2">
|
||||
<Compass className="w-4 h-4 text-indigo-600 shrink-0" />
|
||||
<span className="font-bold">قاعده گرد کردن هوشمند فعال:</span>
|
||||
<span className="font-mono font-bold bg-white px-2 py-0.5 rounded-lg border border-indigo-200">
|
||||
{pricingSettings.roundingStep.toLocaleString('fa-IR')} تومان
|
||||
</span>
|
||||
<span>
|
||||
({pricingSettings.roundingMode === 'UP' ? 'گرد به بالا / سقف' : pricingSettings.roundingMode === 'DOWN' ? 'گرد به پایین / کف' : 'گرد به نزدیکترین مضرب'})
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-[11px] text-gray-500">
|
||||
هنگام تغییر قیمت خرید یا درصد سود، قیمت نهایی خودکار با این قاعده رند میشود.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pt-2">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-bold text-gray-700">موجودی *</label>
|
||||
@ -2233,6 +2426,470 @@ export default function Products() {
|
||||
)}
|
||||
|
||||
|
||||
{/* Bulk Price & Margins Management Modal */}
|
||||
{isBulkPriceModalOpen && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-3 sm:p-4 bg-black/60 backdrop-blur-sm animate-in fade-in duration-200">
|
||||
<div className="bg-white rounded-3xl w-full max-w-2xl max-h-[92vh] flex flex-col shadow-2xl overflow-hidden border border-gray-100 animate-in zoom-in-95 duration-200">
|
||||
{/* Modal Header */}
|
||||
<div className="px-5 sm:px-6 py-4 border-b border-gray-100 flex justify-between items-center bg-white shrink-0">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="w-9 h-9 rounded-xl bg-purple-50 text-purple-600 flex items-center justify-center">
|
||||
<Calculator className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-base font-black text-gray-900">
|
||||
مدیریت و تغییرات گروهی قیمتها
|
||||
</h3>
|
||||
<p className="text-[11px] text-gray-500 mt-0.5">
|
||||
افزایش/کاهش درصدی یا مبلغی و اعمال فرمول سراسری سود بر روی محصولات
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsBulkPriceModalOpen(false)}
|
||||
className="w-8 h-8 rounded-xl bg-gray-50 text-gray-400 hover:text-gray-700 hover:bg-gray-100 flex items-center justify-center transition-colors cursor-pointer"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Modal Tabs */}
|
||||
<div className="flex border-b border-gray-100 px-5 sm:px-6 pt-2 gap-3 shrink-0 bg-gray-50/50">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBulkPriceTab('adjust')}
|
||||
className={`pb-3 px-2 text-xs sm:text-sm font-bold border-b-2 whitespace-nowrap transition-colors cursor-pointer flex items-center gap-1.5 ${
|
||||
bulkPriceTab === 'adjust'
|
||||
? 'border-purple-600 text-purple-700'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
<TrendingUp className="w-4 h-4" />
|
||||
<span>تغییرات گروهی قیمت (افزایش / کاهش)</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBulkPriceTab('margin')}
|
||||
className={`pb-3 px-2 text-xs sm:text-sm font-bold border-b-2 whitespace-nowrap transition-colors cursor-pointer flex items-center gap-1.5 ${
|
||||
bulkPriceTab === 'margin'
|
||||
? 'border-purple-600 text-purple-700'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
<Percent className="w-4 h-4" />
|
||||
<span>اعمال سراسری درصد سود فرمولی</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Modal Body */}
|
||||
<div className="p-5 sm:p-6 overflow-y-auto flex-1 space-y-5 bg-gray-50/30">
|
||||
{bulkPriceTab === 'adjust' ? (
|
||||
/* Tab 1: Bulk Price Adjustment */
|
||||
<div className="space-y-5">
|
||||
{/* Scope */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-gray-700 mb-2">
|
||||
دامنه اعمال تغییرات:
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBulkAdjustForm({ ...bulkAdjustForm, scope: 'ALL' })}
|
||||
className={`p-3 rounded-xl border text-right transition-all cursor-pointer ${
|
||||
bulkAdjustForm.scope === 'ALL'
|
||||
? 'bg-purple-50 border-purple-500 text-purple-900 ring-2 ring-purple-200'
|
||||
: 'bg-white border-gray-200 text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<span className="block text-xs font-bold">همه محصولات فروشگاه</span>
|
||||
<span className="block text-[10px] text-gray-500 mt-0.5">اعمال بر روی تمامی کالاها</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBulkAdjustForm({ ...bulkAdjustForm, scope: 'CATEGORY' })}
|
||||
className={`p-3 rounded-xl border text-right transition-all cursor-pointer ${
|
||||
bulkAdjustForm.scope === 'CATEGORY'
|
||||
? 'bg-purple-50 border-purple-500 text-purple-900 ring-2 ring-purple-200'
|
||||
: 'bg-white border-gray-200 text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<span className="block text-xs font-bold">دستهبندی خاص</span>
|
||||
<span className="block text-[10px] text-gray-500 mt-0.5">انتخاب دستههای مشخص</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{bulkAdjustForm.scope === 'CATEGORY' && (
|
||||
<div className="mt-3 p-3 bg-white rounded-xl border border-gray-200 space-y-2">
|
||||
<span className="block text-[11px] font-bold text-gray-600">انتخاب دستهبندیها:</span>
|
||||
<div className="flex flex-wrap gap-2 max-h-36 overflow-y-auto">
|
||||
{categories.map((c) => {
|
||||
const isSelected = bulkAdjustForm.categoryIds.includes(c.id);
|
||||
return (
|
||||
<button
|
||||
key={c.id}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (isSelected) {
|
||||
setBulkAdjustForm({
|
||||
...bulkAdjustForm,
|
||||
categoryIds: bulkAdjustForm.categoryIds.filter((id) => id !== c.id),
|
||||
});
|
||||
} else {
|
||||
setBulkAdjustForm({
|
||||
...bulkAdjustForm,
|
||||
categoryIds: [...bulkAdjustForm.categoryIds, c.id],
|
||||
});
|
||||
}
|
||||
}}
|
||||
className={`px-3 py-1 rounded-lg text-xs font-bold border transition-all cursor-pointer ${
|
||||
isSelected
|
||||
? 'bg-purple-600 text-white border-purple-600'
|
||||
: 'bg-gray-50 text-gray-700 border-gray-200 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
{c.nameFa || c.name}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Target Field */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-gray-700 mb-2">
|
||||
فیلد هدف برای تغییر قیمت:
|
||||
</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||
{[
|
||||
{ id: 'RETAIL', label: 'قیمت تکفروشی' },
|
||||
{ id: 'WHOLESALE', label: 'قیمت عمده (B2B)' },
|
||||
{ id: 'BOTH_SELLING', label: 'هر دو قیمت فروش' },
|
||||
{ id: 'BUY', label: 'قیمت خرید ما' },
|
||||
{ id: 'ALL', label: 'همه فیلدهای قیمت' },
|
||||
].map((item) => (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
onClick={() => setBulkAdjustForm({ ...bulkAdjustForm, targetField: item.id as any })}
|
||||
className={`p-2.5 rounded-xl border text-center text-xs font-bold transition-all cursor-pointer ${
|
||||
bulkAdjustForm.targetField === item.id
|
||||
? 'bg-purple-600 text-white border-purple-600 shadow-sm'
|
||||
: 'bg-white text-gray-700 border-gray-200 hover:border-purple-200'
|
||||
}`}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Type & Direction */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{/* Direction */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-gray-700 mb-1.5">
|
||||
جهت تغییر:
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBulkAdjustForm({ ...bulkAdjustForm, adjustmentDirection: 'INCREASE' })}
|
||||
className={`p-2.5 rounded-xl border flex items-center justify-center gap-1.5 text-xs font-bold transition-all cursor-pointer ${
|
||||
bulkAdjustForm.adjustmentDirection === 'INCREASE'
|
||||
? 'bg-emerald-600 text-white border-emerald-600 shadow-sm'
|
||||
: 'bg-white text-gray-700 border-gray-200 hover:border-emerald-300'
|
||||
}`}
|
||||
>
|
||||
<TrendingUp className="w-4 h-4" />
|
||||
<span>افزایش (گرانتر)</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBulkAdjustForm({ ...bulkAdjustForm, adjustmentDirection: 'DECREASE' })}
|
||||
className={`p-2.5 rounded-xl border flex items-center justify-center gap-1.5 text-xs font-bold transition-all cursor-pointer ${
|
||||
bulkAdjustForm.adjustmentDirection === 'DECREASE'
|
||||
? 'bg-rose-600 text-white border-rose-600 shadow-sm'
|
||||
: 'bg-white text-gray-700 border-gray-200 hover:border-rose-300'
|
||||
}`}
|
||||
>
|
||||
<TrendingDown className="w-4 h-4" />
|
||||
<span>کاهش (ارزانتر)</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Adjustment Type */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-gray-700 mb-1.5">
|
||||
نوع تغییر:
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBulkAdjustForm({ ...bulkAdjustForm, adjustmentType: 'PERCENT' })}
|
||||
className={`p-2.5 rounded-xl border flex items-center justify-center gap-1.5 text-xs font-bold transition-all cursor-pointer ${
|
||||
bulkAdjustForm.adjustmentType === 'PERCENT'
|
||||
? 'bg-purple-600 text-white border-purple-600 shadow-sm'
|
||||
: 'bg-white text-gray-700 border-gray-200 hover:border-purple-300'
|
||||
}`}
|
||||
>
|
||||
<Percent className="w-4 h-4" />
|
||||
<span>درصدی (%)</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBulkAdjustForm({ ...bulkAdjustForm, adjustmentType: 'FIXED' })}
|
||||
className={`p-2.5 rounded-xl border flex items-center justify-center gap-1.5 text-xs font-bold transition-all cursor-pointer ${
|
||||
bulkAdjustForm.adjustmentType === 'FIXED'
|
||||
? 'bg-purple-600 text-white border-purple-600 shadow-sm'
|
||||
: 'bg-white text-gray-700 border-gray-200 hover:border-purple-300'
|
||||
}`}
|
||||
>
|
||||
<DollarSign className="w-4 h-4" />
|
||||
<span>مبلغ ثابت (تومان)</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Value Input */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-gray-700 mb-1.5">
|
||||
مقدار تغییر {bulkAdjustForm.adjustmentType === 'PERCENT' ? '(درصد)' : '(تومان)'}:
|
||||
</label>
|
||||
{bulkAdjustForm.adjustmentType === 'PERCENT' ? (
|
||||
<div className="relative">
|
||||
<input
|
||||
type="number"
|
||||
min="0.1"
|
||||
max="200"
|
||||
step="0.5"
|
||||
value={bulkAdjustForm.value}
|
||||
onChange={(e) => setBulkAdjustForm({ ...bulkAdjustForm, value: Number(e.target.value) || 0 })}
|
||||
className="w-full px-4 py-2.5 bg-white rounded-xl border border-gray-200 focus:border-purple-500 outline-none text-sm font-bold"
|
||||
dir="ltr"
|
||||
/>
|
||||
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 font-bold text-xs">%</span>
|
||||
</div>
|
||||
) : (
|
||||
<PriceInput
|
||||
value={String(bulkAdjustForm.value)}
|
||||
onChange={(val) => setBulkAdjustForm({ ...bulkAdjustForm, value: Number(val) || 0 })}
|
||||
placeholder="مثال: ۱۰۰,۰۰۰"
|
||||
className="px-4 py-2.5 bg-white rounded-xl border border-gray-200 focus:border-purple-500 text-sm font-bold"
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex flex-wrap gap-1.5 mt-2">
|
||||
{bulkAdjustForm.adjustmentType === 'PERCENT'
|
||||
? [5, 10, 15, 20, 25, 30].map((pVal) => (
|
||||
<button
|
||||
key={pVal}
|
||||
type="button"
|
||||
onClick={() => setBulkAdjustForm({ ...bulkAdjustForm, value: pVal })}
|
||||
className="px-2.5 py-1 bg-white text-gray-600 hover:text-purple-600 text-xs font-bold rounded-lg border border-gray-200 hover:border-purple-200 cursor-pointer"
|
||||
>
|
||||
{pVal}٪
|
||||
</button>
|
||||
))
|
||||
: [50000, 100000, 200000, 500000].map((fVal) => (
|
||||
<button
|
||||
key={fVal}
|
||||
type="button"
|
||||
onClick={() => setBulkAdjustForm({ ...bulkAdjustForm, value: fVal })}
|
||||
className="px-2.5 py-1 bg-white text-gray-600 hover:text-purple-600 text-xs font-bold rounded-lg border border-gray-200 hover:border-purple-200 cursor-pointer"
|
||||
>
|
||||
{fVal.toLocaleString('fa-IR')} تومان
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Apply Rounding Toggle */}
|
||||
<div className="p-3.5 bg-indigo-50/70 rounded-2xl border border-indigo-100 flex items-center justify-between">
|
||||
<label className="flex items-center gap-2.5 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={bulkAdjustForm.applyRounding}
|
||||
onChange={(e) => setBulkAdjustForm({ ...bulkAdjustForm, applyRounding: e.target.checked })}
|
||||
className="w-4 h-4 rounded text-indigo-600 focus:ring-indigo-500 border-gray-300"
|
||||
/>
|
||||
<div>
|
||||
<span className="block text-xs font-bold text-indigo-950">
|
||||
اعمال خودکار گرد کردن بر روی قیمتهای جدید
|
||||
</span>
|
||||
<span className="block text-[11px] text-indigo-700 mt-0.5">
|
||||
گام: {pricingSettings.roundingStep.toLocaleString('fa-IR')} تومان ({pricingSettings.roundingMode === 'UP' ? 'به بالا' : pricingSettings.roundingMode === 'DOWN' ? 'به پایین' : 'به نزدیکترین'})
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
/* Tab 2: Global Formula Margins */
|
||||
<div className="space-y-5">
|
||||
<div className="p-4 bg-gradient-to-br from-indigo-50 to-purple-50 rounded-2xl border border-indigo-100 text-xs leading-relaxed text-indigo-900 space-y-1">
|
||||
<p className="font-black flex items-center gap-1.5">
|
||||
<Compass className="w-4 h-4 text-indigo-600" />
|
||||
منطق و اولویت تغییرات سراسری درصد سود:
|
||||
</p>
|
||||
<p className="text-gray-600 text-[11px]">
|
||||
با اجرای این عملیات، قیمتهای فروش تمام کالاهایی که «قیمت خرید» دارند، مجدداً با فرمول <span className="font-bold font-mono">قیمت خرید × (۱ + درصد سود)</span> محاسبه و بر اساس قاعده گرد کردن تنظیمشده (پله {pricingSettings.roundingStep.toLocaleString('fa-IR')} تومان {pricingSettings.roundingMode === 'UP' ? 'به بالا' : pricingSettings.roundingMode === 'DOWN' ? 'به پایین' : 'به نزدیکترین'}) رند میشوند.
|
||||
</p>
|
||||
<p className="text-[11px] text-purple-700 font-bold">
|
||||
✨ قانون اولویت: هر تغییر جدیدتر (چه در اینجا و چه بعداً در صفحه ویرایش محصول) ملاک نهایی قیمت خواهد بود.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Scope */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-gray-700 mb-2">
|
||||
دامنه اعمال فرمول:
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setGlobalMarginForm({ ...globalMarginForm, scope: 'ALL' })}
|
||||
className={`p-3 rounded-xl border text-right transition-all cursor-pointer ${
|
||||
globalMarginForm.scope === 'ALL'
|
||||
? 'bg-purple-50 border-purple-500 text-purple-900 ring-2 ring-purple-200'
|
||||
: 'bg-white border-gray-200 text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<span className="block text-xs font-bold">همه محصولات فروشگاه</span>
|
||||
<span className="block text-[10px] text-gray-500 mt-0.5">محاسبه برای تمامی کالاها</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setGlobalMarginForm({ ...globalMarginForm, scope: 'CATEGORY' })}
|
||||
className={`p-3 rounded-xl border text-right transition-all cursor-pointer ${
|
||||
globalMarginForm.scope === 'CATEGORY'
|
||||
? 'bg-purple-50 border-purple-500 text-purple-900 ring-2 ring-purple-200'
|
||||
: 'bg-white border-gray-200 text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<span className="block text-xs font-bold">دستهبندی خاص</span>
|
||||
<span className="block text-[10px] text-gray-500 mt-0.5">انتخاب دستههای مشخص</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{globalMarginForm.scope === 'CATEGORY' && (
|
||||
<div className="mt-3 p-3 bg-white rounded-xl border border-gray-200 space-y-2">
|
||||
<span className="block text-[11px] font-bold text-gray-600">انتخاب دستهبندیها:</span>
|
||||
<div className="flex flex-wrap gap-2 max-h-36 overflow-y-auto">
|
||||
{categories.map((c) => {
|
||||
const isSelected = globalMarginForm.categoryIds.includes(c.id);
|
||||
return (
|
||||
<button
|
||||
key={c.id}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (isSelected) {
|
||||
setGlobalMarginForm({
|
||||
...globalMarginForm,
|
||||
categoryIds: globalMarginForm.categoryIds.filter((id) => id !== c.id),
|
||||
});
|
||||
} else {
|
||||
setGlobalMarginForm({
|
||||
...globalMarginForm,
|
||||
categoryIds: [...globalMarginForm.categoryIds, c.id],
|
||||
});
|
||||
}
|
||||
}}
|
||||
className={`px-3 py-1 rounded-lg text-xs font-bold border transition-all cursor-pointer ${
|
||||
isSelected
|
||||
? 'bg-purple-600 text-white border-purple-600'
|
||||
: 'bg-gray-50 text-gray-700 border-gray-200 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
{c.nameFa || c.name}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{/* Retail Margin */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-gray-700 mb-1.5">
|
||||
درصد سود تکفروشی جدید:
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="500"
|
||||
step="0.5"
|
||||
value={globalMarginForm.retailMarginPercent}
|
||||
onChange={(e) => setGlobalMarginForm({ ...globalMarginForm, retailMarginPercent: Number(e.target.value) || 0 })}
|
||||
className="w-full px-4 py-2.5 bg-white rounded-xl border border-gray-200 focus:border-purple-500 outline-none text-sm font-bold"
|
||||
dir="ltr"
|
||||
/>
|
||||
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 font-bold text-xs">%</span>
|
||||
</div>
|
||||
<p className="text-[11px] text-gray-400 mt-1">پیشفرض سیستم: {pricingSettings.defaultRetailMarginPercent}٪</p>
|
||||
</div>
|
||||
|
||||
{/* Wholesale Margin */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-gray-700 mb-1.5">
|
||||
درصد سود عمدهفروشی (B2B) جدید:
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="500"
|
||||
step="0.5"
|
||||
value={globalMarginForm.wholesaleMarginPercent}
|
||||
onChange={(e) => setGlobalMarginForm({ ...globalMarginForm, wholesaleMarginPercent: Number(e.target.value) || 0 })}
|
||||
className="w-full px-4 py-2.5 bg-white rounded-xl border border-gray-200 focus:border-purple-500 outline-none text-sm font-bold"
|
||||
dir="ltr"
|
||||
/>
|
||||
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 font-bold text-xs">%</span>
|
||||
</div>
|
||||
<p className="text-[11px] text-gray-400 mt-1">پیشفرض سیستم: {pricingSettings.defaultWholesaleMarginPercent}٪</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Modal Footer */}
|
||||
<div className="px-5 sm:px-6 py-4 border-t border-gray-100 flex justify-between items-center bg-white shrink-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setIsBulkPriceModalOpen(false)}
|
||||
disabled={isBulkApplying}
|
||||
>
|
||||
انصراف
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
startIcon={isBulkApplying ? Spinner : CheckCircle2}
|
||||
isLoading={isBulkApplying}
|
||||
onClick={bulkPriceTab === 'adjust' ? handleBulkPriceAdjustment : handleApplyGlobalMargins}
|
||||
>
|
||||
{bulkPriceTab === 'adjust' ? 'اعمال تغییرات قیمت بر روی کالاها' : 'محاسبه و اعمال سراسری سود'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Media Selector Modal */}
|
||||
<MediaSelector
|
||||
isOpen={isMediaSelectorOpen}
|
||||
|
||||
@ -213,4 +213,11 @@ export interface Product {
|
||||
stockQuantity?: number;
|
||||
}
|
||||
|
||||
export interface PricingSettings {
|
||||
roundingStep: number;
|
||||
roundingMode: 'UP' | 'DOWN' | 'NEAREST';
|
||||
defaultRetailMarginPercent: number;
|
||||
defaultWholesaleMarginPercent: number;
|
||||
}
|
||||
|
||||
|
||||
|
||||
43
frontend/admin-panel/src/utils/pricing.ts
Normal file
43
frontend/admin-panel/src/utils/pricing.ts
Normal file
@ -0,0 +1,43 @@
|
||||
export type RoundingMode = 'UP' | 'DOWN' | 'NEAREST';
|
||||
|
||||
export interface PricingSettings {
|
||||
roundingStep: number;
|
||||
roundingMode: RoundingMode;
|
||||
defaultRetailMarginPercent: number;
|
||||
defaultWholesaleMarginPercent: number;
|
||||
}
|
||||
|
||||
export const DEFAULT_PRICING_SETTINGS: PricingSettings = {
|
||||
roundingStep: 5000,
|
||||
roundingMode: 'UP',
|
||||
defaultRetailMarginPercent: 30,
|
||||
defaultWholesaleMarginPercent: 15,
|
||||
};
|
||||
|
||||
export function applyRounding(
|
||||
price: number,
|
||||
step: number = 5000,
|
||||
mode: RoundingMode = 'UP',
|
||||
): number {
|
||||
if (isNaN(price) || price <= 0) return 0;
|
||||
if (!step || step <= 1) return Math.round(price);
|
||||
|
||||
if (mode === 'UP') {
|
||||
return Math.ceil(price / step) * step;
|
||||
} else if (mode === 'DOWN') {
|
||||
return Math.floor(price / step) * step;
|
||||
} else {
|
||||
return Math.round(price / step) * step;
|
||||
}
|
||||
}
|
||||
|
||||
export function calculateSellingPrice(
|
||||
buyPrice: number,
|
||||
marginPercent: number,
|
||||
step: number = 5000,
|
||||
mode: RoundingMode = 'UP',
|
||||
): number {
|
||||
if (isNaN(buyPrice) || buyPrice <= 0) return 0;
|
||||
const rawPrice = buyPrice * (1 + (marginPercent || 0) / 100);
|
||||
return applyRounding(rawPrice, step, mode);
|
||||
}
|
||||
@ -3,14 +3,14 @@
|
||||
"1": "app.module.ts",
|
||||
"2": "PaymentService",
|
||||
"3": "productService.ts",
|
||||
"4": "UserDashboard.tsx",
|
||||
"4": "PetProfile.tsx",
|
||||
"5": "CmsController",
|
||||
"6": "tickets.controller.ts",
|
||||
"7": "Button.tsx",
|
||||
"8": "WikiController",
|
||||
"9": "devDependencies",
|
||||
"10": "CreateReviewDto",
|
||||
"11": "MediaSelector.tsx",
|
||||
"11": "ConfirmModal.tsx",
|
||||
"12": "index.ts",
|
||||
"13": "app-audit-verification.e2e-spec.js",
|
||||
"14": "toPersian",
|
||||
@ -18,7 +18,7 @@
|
||||
"16": "DoctorQueryDto",
|
||||
"17": "schema.ts",
|
||||
"18": "JwtAuthGuard",
|
||||
"19": "users.controller.ts",
|
||||
"19": "admin.service.ts",
|
||||
"20": "CreateVideoDto",
|
||||
"21": "ReportsController",
|
||||
"22": "SmsService",
|
||||
@ -32,14 +32,14 @@
|
||||
"30": "DEVOPS-001",
|
||||
"31": "DOC-001",
|
||||
"32": "adminRoutes.tsx",
|
||||
"33": "ContactService",
|
||||
"33": "WholesaleApplyDto",
|
||||
"34": "B2BService",
|
||||
"35": "AuthController",
|
||||
"36": "FaqController",
|
||||
"35": "ContactService",
|
||||
"36": "FaqService",
|
||||
"37": "راهنمای تست سیستم (Software Testing)",
|
||||
"38": "Transactions.tsx",
|
||||
"38": "Button",
|
||||
"39": "CategoriesController",
|
||||
"40": "admin.module.ts",
|
||||
"40": "MediaController",
|
||||
"41": "What You Must Do When Invoked",
|
||||
"42": "SslController",
|
||||
"43": "BannersService",
|
||||
@ -51,21 +51,21 @@
|
||||
"49": "devDependencies",
|
||||
"50": "devDependencies",
|
||||
"51": "BlogsController",
|
||||
"52": "PrescriptionsService",
|
||||
"52": "prescriptions.controller.ts",
|
||||
"53": "SmartAdvisorService",
|
||||
"54": "Modal.tsx",
|
||||
"55": "UITexts.tsx",
|
||||
"56": "Orders.tsx",
|
||||
"56": "PrescriptionsManager.tsx",
|
||||
"57": "Role & Core Objective",
|
||||
"58": "auth.module.ts",
|
||||
"58": "UserDashboard.tsx",
|
||||
"59": "compilerOptions",
|
||||
"60": "admin.service.ts",
|
||||
"60": "CreateUserDto",
|
||||
"61": "ProductPage.tsx",
|
||||
"62": "UsersService",
|
||||
"62": "BlogsService",
|
||||
"63": "dependencies",
|
||||
"64": "compilerOptions",
|
||||
"65": "BlogsService",
|
||||
"66": "ApiOperation",
|
||||
"65": "RevalidationService",
|
||||
"66": "Get",
|
||||
"67": "PetsController",
|
||||
"68": "useSettingsStore",
|
||||
"69": "Required Review Group Closures",
|
||||
@ -84,17 +84,17 @@
|
||||
"82": "scripts",
|
||||
"83": "dependencies",
|
||||
"84": "Role & Core Objective",
|
||||
"85": "Reports.tsx",
|
||||
"85": "torob.controller.ts",
|
||||
"86": "zibal.service.ts",
|
||||
"87": "dependencies",
|
||||
"88": "CreateEBankCheckoutDto",
|
||||
"89": "seed-products.ts",
|
||||
"90": "useCartStore",
|
||||
"90": "OrderService",
|
||||
"91": "Reconciled Audit Roles & Assignments",
|
||||
"92": "OrdersService",
|
||||
"93": "ArchivePage.tsx",
|
||||
"94": "Phase 3.1 — Human Review Preparation and Master Backlog Critique",
|
||||
"95": "blog/[slug]/page.tsx",
|
||||
"95": "wiki/[slug]/page.tsx",
|
||||
"96": "compilerOptions",
|
||||
"97": "InitiatePaymentDto",
|
||||
"98": "scripts",
|
||||
@ -103,18 +103,18 @@
|
||||
"101": "Operational Rules & Boundaries",
|
||||
"102": "jest",
|
||||
"103": "Comprehensive Change Log",
|
||||
"104": "Coupons.tsx",
|
||||
"104": "Products.tsx",
|
||||
"105": "Operational Rules & Boundaries",
|
||||
"106": "AuthService",
|
||||
"106": "RedisService",
|
||||
"107": "PaginationDto",
|
||||
"108": "PrismaService",
|
||||
"109": "1. Summary of Integrity Repairs Performed",
|
||||
"110": "Operational Rules & Boundaries",
|
||||
"111": "Operational Rules & Boundaries",
|
||||
"112": "Operational Rules & Boundaries",
|
||||
"113": "RegisterDto",
|
||||
"113": "Orders.tsx",
|
||||
"114": "AppService",
|
||||
"115": "Blogs.tsx",
|
||||
"115": "MediaSelector.tsx",
|
||||
"116": "Vazirmatn Changelog",
|
||||
"117": "Vazirmatn Font فونت وزیرمتن",
|
||||
"118": "Operational Rules & Boundaries",
|
||||
@ -122,7 +122,7 @@
|
||||
"120": "compilerOptions",
|
||||
"121": "backend/README.md",
|
||||
"122": "AdminService",
|
||||
"123": "UsersController",
|
||||
"123": "UsersService",
|
||||
"124": "Repository Map",
|
||||
"125": "validate_integrity.js",
|
||||
"126": "admin-panel/package.json",
|
||||
@ -143,15 +143,15 @@
|
||||
"141": "application/package.json",
|
||||
"142": "start-dev.js",
|
||||
"143": "generate-openapi.js",
|
||||
"144": "PodcastPlayerModal.tsx",
|
||||
"145": "FaqService",
|
||||
"144": "SafeImage.tsx",
|
||||
"145": "AuthService",
|
||||
"146": "System Discovery",
|
||||
"147": "HomeController",
|
||||
"148": "class-transformer",
|
||||
"149": "SmsSettingsPage.tsx",
|
||||
"148": "track/page.tsx",
|
||||
"149": "trust-seals/page.tsx",
|
||||
"150": "Product Requirement Document (PRD)",
|
||||
"151": "helmet",
|
||||
"152": "RedisService",
|
||||
"151": "ValidateCouponDto",
|
||||
"152": "MetricsController",
|
||||
"153": "exclude",
|
||||
"154": "Baseline Command Plan & Reconciled Command History",
|
||||
"155": "seo-backfill.ts",
|
||||
@ -176,15 +176,14 @@
|
||||
"174": "rebuild_honest_ledger.js",
|
||||
"175": "validate_evidence_grade.js",
|
||||
"176": "uploads/[...path]/route.ts",
|
||||
"177": "videos/page.tsx",
|
||||
"177": "app/page.tsx",
|
||||
"178": "نقشه جامع پوشش تستهای سرتاسری (E2E Test Coverage Map) — پروژه Canina",
|
||||
"179": "app.e2e-spec.js",
|
||||
"179": "@types/node",
|
||||
"180": "API Contract Specification",
|
||||
"181": "⚙️ Backend Technical Review (05_dev_backend)",
|
||||
"182": "🎨 Frontend & Admin Panel Technical Review (06_dev_frontend)",
|
||||
"183": "🚀 SEO & Content Strategy Review (12_seo_content)",
|
||||
"184": "js-yaml",
|
||||
"185": "@nestjs/core",
|
||||
"184": "eslint-config-next",
|
||||
"186": "seed-ui-texts.ts",
|
||||
"187": "seed-wiki.ts",
|
||||
"188": "update-blog.dto.ts",
|
||||
@ -197,13 +196,10 @@
|
||||
"195": "React + TypeScript + Vite",
|
||||
"196": "Select.tsx",
|
||||
"197": "userStore.ts",
|
||||
"198": "@nestjs/jwt",
|
||||
"199": "tailwindcss",
|
||||
"200": "application/README.md",
|
||||
"201": "deploy.sh",
|
||||
"202": "🔒 Security & Performance Review (09_devops_security)",
|
||||
"203": "👁️ UX & Persona Interface Review (08_visual_qa)",
|
||||
"204": "eslint",
|
||||
"205": "prisma/scientificTerms.ts",
|
||||
"206": "seed-blogs.ts",
|
||||
"207": "seed-custom.ts",
|
||||
@ -222,21 +218,13 @@
|
||||
"220": "Input.tsx",
|
||||
"221": "Textarea.tsx",
|
||||
"222": "admin-panel/tsconfig.json",
|
||||
"223": "catalog/page.tsx",
|
||||
"224": "eslint-config-prettier",
|
||||
"225": "next.config.ts",
|
||||
"226": "Shabnam Font README",
|
||||
"227": "AGENTS.md",
|
||||
"228": "rules/graphify.md",
|
||||
"229": ".agents/workflows/graphify.md",
|
||||
"230": "instructions.md",
|
||||
"231": "@nestjs/swagger",
|
||||
"232": "@nestjs/throttler",
|
||||
"233": "tailwindcss",
|
||||
"234": "passport",
|
||||
"235": "reflect-metadata",
|
||||
"236": "swagger-ui-express",
|
||||
"237": "@eslint/eslintrc",
|
||||
"238": "@vitejs/plugin-react",
|
||||
"239": "eslint-plugin-prettier",
|
||||
"240": "supertest",
|
||||
@ -266,7 +254,6 @@
|
||||
"264": "globals",
|
||||
"265": "jest",
|
||||
"266": "@nestjs/cli",
|
||||
"267": "@nestjs/testing",
|
||||
"268": "prettier",
|
||||
"269": "eslint-plugin-react-hooks",
|
||||
"270": "app-audit-verification.e2e-spec.d.ts",
|
||||
@ -294,36 +281,22 @@
|
||||
"292": "Production Docker Compose",
|
||||
"293": "Staging Docker Compose",
|
||||
"294": "ZibalService",
|
||||
"295": "@nestjs/schematics",
|
||||
"296": "ts-jest",
|
||||
"297": "ZibalEBankService",
|
||||
"298": ".initiateOrderPayment",
|
||||
"299": "@tailwindcss/postcss",
|
||||
"300": "typescript",
|
||||
"301": "@types/js-yaml",
|
||||
"302": "typescript-eslint",
|
||||
"303": "prisma",
|
||||
"304": "source-map-support",
|
||||
"305": "@types/supertest",
|
||||
"306": "@eslint/js",
|
||||
"307": "ts-loader",
|
||||
"308": "typescript-eslint",
|
||||
"309": "axios",
|
||||
"310": "tailwindcss",
|
||||
"311": "tsconfig-paths",
|
||||
"312": "@types/passport-jwt",
|
||||
"313": "20260526160916_add_ui_texts_and_scientific_terms/migration.sql",
|
||||
"314": "@types/bcrypt",
|
||||
"315": "typescript",
|
||||
"316": "@types/compression",
|
||||
"317": "revalidate/route.ts",
|
||||
"318": "MaskableField.tsx",
|
||||
"319": "@tailwindcss/postcss",
|
||||
"320": "@types/express",
|
||||
"321": "orders/page.tsx",
|
||||
"322": "pets/page.tsx",
|
||||
"323": "@types/jest",
|
||||
"325": "@types/react-dom",
|
||||
"327": "eslint-plugin-react-refresh",
|
||||
"329": "@types/multer"
|
||||
"327": "eslint-plugin-react-refresh"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -3,13 +3,13 @@
|
||||
"1": "app.module.ts",
|
||||
"2": "PaymentService",
|
||||
"3": "productService.ts",
|
||||
"4": "PetProfile.tsx",
|
||||
"4": "UserDashboard.tsx",
|
||||
"5": "CmsController",
|
||||
"6": "tickets.controller.ts",
|
||||
"7": "Button.tsx",
|
||||
"8": "WikiController",
|
||||
"9": "devDependencies",
|
||||
"10": "ReviewsService",
|
||||
"10": "CreateReviewDto",
|
||||
"11": "MediaSelector.tsx",
|
||||
"12": "index.ts",
|
||||
"13": "app-audit-verification.e2e-spec.js",
|
||||
@ -18,9 +18,9 @@
|
||||
"16": "DoctorQueryDto",
|
||||
"17": "schema.ts",
|
||||
"18": "JwtAuthGuard",
|
||||
"19": "ProductsService",
|
||||
"19": "users.controller.ts",
|
||||
"20": "CreateVideoDto",
|
||||
"21": "admin.module.ts",
|
||||
"21": "ReportsController",
|
||||
"22": "SmsService",
|
||||
"23": "MenuService",
|
||||
"24": "BE-001",
|
||||
@ -32,14 +32,14 @@
|
||||
"30": "DEVOPS-001",
|
||||
"31": "DOC-001",
|
||||
"32": "adminRoutes.tsx",
|
||||
"33": "WholesaleApplyDto",
|
||||
"33": "ContactService",
|
||||
"34": "B2BService",
|
||||
"35": "AuthController",
|
||||
"36": "FaqService",
|
||||
"36": "FaqController",
|
||||
"37": "راهنمای تست سیستم (Software Testing)",
|
||||
"38": "Transactions.tsx",
|
||||
"39": "CategoriesController",
|
||||
"40": "MediaController",
|
||||
"40": "admin.module.ts",
|
||||
"41": "What You Must Do When Invoked",
|
||||
"42": "SslController",
|
||||
"43": "BannersService",
|
||||
@ -57,17 +57,17 @@
|
||||
"55": "UITexts.tsx",
|
||||
"56": "Orders.tsx",
|
||||
"57": "Role & Core Objective",
|
||||
"58": "ContactService",
|
||||
"58": "auth.module.ts",
|
||||
"59": "compilerOptions",
|
||||
"60": "admin.service.ts",
|
||||
"61": "ProductPage.tsx",
|
||||
"62": "RevalidationService",
|
||||
"62": "UsersService",
|
||||
"63": "dependencies",
|
||||
"64": "compilerOptions",
|
||||
"65": "BlogsService",
|
||||
"66": "AdminQueryDto",
|
||||
"66": "ApiOperation",
|
||||
"67": "PetsController",
|
||||
"68": "lib/services/api.ts",
|
||||
"68": "useSettingsStore",
|
||||
"69": "Required Review Group Closures",
|
||||
"70": "compilerOptions",
|
||||
"71": "getPageMetadata",
|
||||
@ -75,7 +75,7 @@
|
||||
"73": "Operational Rules & Boundaries",
|
||||
"74": "WikiController",
|
||||
"75": "PetsController",
|
||||
"76": "ProductsController",
|
||||
"76": "ProductsService",
|
||||
"77": "seo.module.ts",
|
||||
"78": "rss.xml/route.ts",
|
||||
"79": "🏢 AI Software Agency — Master Orchestration Protocol v3",
|
||||
@ -84,15 +84,15 @@
|
||||
"82": "scripts",
|
||||
"83": "dependencies",
|
||||
"84": "Role & Core Objective",
|
||||
"85": "pets/pets.controller.ts",
|
||||
"85": "Reports.tsx",
|
||||
"86": "zibal.service.ts",
|
||||
"87": "dependencies",
|
||||
"88": "CreateEBankCheckoutDto",
|
||||
"89": "seed-products.ts",
|
||||
"90": "UserDashboard.tsx",
|
||||
"90": "useCartStore",
|
||||
"91": "Reconciled Audit Roles & Assignments",
|
||||
"92": "OrdersService",
|
||||
"93": "HomeClient.tsx",
|
||||
"93": "ArchivePage.tsx",
|
||||
"94": "Phase 3.1 — Human Review Preparation and Master Backlog Critique",
|
||||
"95": "blog/[slug]/page.tsx",
|
||||
"96": "compilerOptions",
|
||||
@ -122,13 +122,13 @@
|
||||
"120": "compilerOptions",
|
||||
"121": "backend/README.md",
|
||||
"122": "AdminService",
|
||||
"123": "UsersService",
|
||||
"123": "UsersController",
|
||||
"124": "Repository Map",
|
||||
"125": "validate_integrity.js",
|
||||
"126": "admin-panel/package.json",
|
||||
"127": "Sahel-Font",
|
||||
"128": "AdminController",
|
||||
"129": "AuthService",
|
||||
"129": "WikiService",
|
||||
"130": "Sahel-Font",
|
||||
"131": "Role & Core Objective",
|
||||
"132": "orchestrate.py",
|
||||
@ -143,15 +143,15 @@
|
||||
"141": "application/package.json",
|
||||
"142": "start-dev.js",
|
||||
"143": "generate-openapi.js",
|
||||
"144": "SafeImage.tsx",
|
||||
"145": "TorobController",
|
||||
"144": "PodcastPlayerModal.tsx",
|
||||
"145": "FaqService",
|
||||
"146": "System Discovery",
|
||||
"147": "HomeController",
|
||||
"148": "CreateUserDto",
|
||||
"148": "class-transformer",
|
||||
"149": "SmsSettingsPage.tsx",
|
||||
"150": "Product Requirement Document (PRD)",
|
||||
"151": "CreateReviewDto",
|
||||
"152": "MetricsController",
|
||||
"151": "helmet",
|
||||
"152": "RedisService",
|
||||
"153": "exclude",
|
||||
"154": "Baseline Command Plan & Reconciled Command History",
|
||||
"155": "seo-backfill.ts",
|
||||
@ -183,8 +183,8 @@
|
||||
"181": "⚙️ Backend Technical Review (05_dev_backend)",
|
||||
"182": "🎨 Frontend & Admin Panel Technical Review (06_dev_frontend)",
|
||||
"183": "🚀 SEO & Content Strategy Review (12_seo_content)",
|
||||
"184": "UpdateReviewDto",
|
||||
"185": "app/page.tsx",
|
||||
"184": "js-yaml",
|
||||
"185": "@nestjs/core",
|
||||
"186": "seed-ui-texts.ts",
|
||||
"187": "seed-wiki.ts",
|
||||
"188": "update-blog.dto.ts",
|
||||
@ -196,8 +196,8 @@
|
||||
"194": "Raw Finding Verification & Disposition Report",
|
||||
"195": "React + TypeScript + Vite",
|
||||
"196": "Select.tsx",
|
||||
"197": "useSettingsStore",
|
||||
"198": "@types/node",
|
||||
"197": "userStore.ts",
|
||||
"198": "@nestjs/jwt",
|
||||
"199": "tailwindcss",
|
||||
"200": "application/README.md",
|
||||
"201": "deploy.sh",
|
||||
@ -230,11 +230,15 @@
|
||||
"228": "rules/graphify.md",
|
||||
"229": ".agents/workflows/graphify.md",
|
||||
"230": "instructions.md",
|
||||
"231": "track/page.tsx",
|
||||
"232": "typescript",
|
||||
"231": "@nestjs/swagger",
|
||||
"232": "@nestjs/throttler",
|
||||
"233": "tailwindcss",
|
||||
"234": "eslint-config-next",
|
||||
"234": "passport",
|
||||
"235": "reflect-metadata",
|
||||
"236": "swagger-ui-express",
|
||||
"237": "@eslint/eslintrc",
|
||||
"238": "@vitejs/plugin-react",
|
||||
"239": "eslint-plugin-prettier",
|
||||
"240": "supertest",
|
||||
"241": "blog.entity.ts",
|
||||
"242": "home.entity.ts",
|
||||
@ -259,6 +263,11 @@
|
||||
"261": "User Login API",
|
||||
"262": "User Logout API",
|
||||
"263": "generate-openapi.d.ts",
|
||||
"264": "globals",
|
||||
"265": "jest",
|
||||
"266": "@nestjs/cli",
|
||||
"267": "@nestjs/testing",
|
||||
"268": "prettier",
|
||||
"269": "eslint-plugin-react-hooks",
|
||||
"270": "app-audit-verification.e2e-spec.d.ts",
|
||||
"271": "app.e2e-spec.d.ts",
|
||||
@ -286,16 +295,19 @@
|
||||
"293": "Staging Docker Compose",
|
||||
"294": "ZibalService",
|
||||
"295": "@nestjs/schematics",
|
||||
"296": "ts-jest",
|
||||
"297": "ZibalEBankService",
|
||||
"298": ".initiateOrderPayment",
|
||||
"299": "@tailwindcss/postcss",
|
||||
"300": "typescript",
|
||||
"301": "@types/js-yaml",
|
||||
"302": "typescript-eslint",
|
||||
"303": "prisma",
|
||||
"304": "source-map-support",
|
||||
"305": "@types/supertest",
|
||||
"306": "@eslint/js",
|
||||
"307": "ts-loader",
|
||||
"308": "ts-node",
|
||||
"308": "typescript-eslint",
|
||||
"309": "axios",
|
||||
"310": "tailwindcss",
|
||||
"311": "tsconfig-paths",
|
||||
@ -306,6 +318,7 @@
|
||||
"316": "@types/compression",
|
||||
"317": "revalidate/route.ts",
|
||||
"318": "MaskableField.tsx",
|
||||
"319": "@tailwindcss/postcss",
|
||||
"320": "@types/express",
|
||||
"321": "orders/page.tsx",
|
||||
"322": "pets/page.tsx",
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
# Graph Report - canina (2026-08-29)
|
||||
# Graph Report - canina (2026-09-02)
|
||||
|
||||
## Corpus Check
|
||||
- 596 files · ~1,071,427 words
|
||||
- 596 files · ~1,072,182 words
|
||||
- Verdict: corpus is large enough that graph structure adds value.
|
||||
|
||||
## Summary
|
||||
- 4169 nodes · 7522 edges · 314 communities (214 shown, 100 thin omitted)
|
||||
- 4173 nodes · 7530 edges · 327 communities (211 shown, 116 thin omitted)
|
||||
- Extraction: 96% EXTRACTED · 4% INFERRED · 0% AMBIGUOUS · INFERRED: 281 edges (avg confidence: 0.79)
|
||||
- Token cost: 0 input · 0 output
|
||||
|
||||
## Graph Freshness
|
||||
- Built from commit: `376fea35`
|
||||
- Built from commit: `88060431`
|
||||
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
|
||||
- Run `graphify update .` after code changes (no API cost).
|
||||
|
||||
@ -19,13 +19,13 @@
|
||||
- app.module.ts
|
||||
- PaymentService
|
||||
- productService.ts
|
||||
- PetProfile.tsx
|
||||
- UserDashboard.tsx
|
||||
- CmsController
|
||||
- tickets.controller.ts
|
||||
- Button.tsx
|
||||
- WikiController
|
||||
- devDependencies
|
||||
- ReviewsService
|
||||
- CreateReviewDto
|
||||
- MediaSelector.tsx
|
||||
- index.ts
|
||||
- app-audit-verification.e2e-spec.js
|
||||
@ -34,9 +34,9 @@
|
||||
- DoctorQueryDto
|
||||
- schema.ts
|
||||
- JwtAuthGuard
|
||||
- ProductsService
|
||||
- users.controller.ts
|
||||
- CreateVideoDto
|
||||
- admin.module.ts
|
||||
- ReportsController
|
||||
- SmsService
|
||||
- MenuService
|
||||
- BE-001
|
||||
@ -48,14 +48,14 @@
|
||||
- DEVOPS-001
|
||||
- DOC-001
|
||||
- adminRoutes.tsx
|
||||
- WholesaleApplyDto
|
||||
- ContactService
|
||||
- B2BService
|
||||
- AuthController
|
||||
- FaqService
|
||||
- FaqController
|
||||
- راهنمای تست سیستم (Software Testing)
|
||||
- Transactions.tsx
|
||||
- CategoriesController
|
||||
- MediaController
|
||||
- admin.module.ts
|
||||
- What You Must Do When Invoked
|
||||
- SslController
|
||||
- BannersService
|
||||
@ -73,17 +73,17 @@
|
||||
- UITexts.tsx
|
||||
- Orders.tsx
|
||||
- Role & Core Objective
|
||||
- ContactService
|
||||
- auth.module.ts
|
||||
- compilerOptions
|
||||
- admin.service.ts
|
||||
- ProductPage.tsx
|
||||
- RevalidationService
|
||||
- UsersService
|
||||
- dependencies
|
||||
- compilerOptions
|
||||
- BlogsService
|
||||
- AdminQueryDto
|
||||
- ApiOperation
|
||||
- PetsController
|
||||
- lib/services/api.ts
|
||||
- useSettingsStore
|
||||
- Required Review Group Closures
|
||||
- compilerOptions
|
||||
- getPageMetadata
|
||||
@ -91,7 +91,7 @@
|
||||
- Operational Rules & Boundaries
|
||||
- WikiController
|
||||
- PetsController
|
||||
- ProductsController
|
||||
- ProductsService
|
||||
- seo.module.ts
|
||||
- rss.xml/route.ts
|
||||
- 🏢 AI Software Agency — Master Orchestration Protocol v3
|
||||
@ -100,15 +100,15 @@
|
||||
- scripts
|
||||
- dependencies
|
||||
- Role & Core Objective
|
||||
- pets/pets.controller.ts
|
||||
- Reports.tsx
|
||||
- zibal.service.ts
|
||||
- dependencies
|
||||
- CreateEBankCheckoutDto
|
||||
- seed-products.ts
|
||||
- UserDashboard.tsx
|
||||
- useCartStore
|
||||
- Reconciled Audit Roles & Assignments
|
||||
- OrdersService
|
||||
- HomeClient.tsx
|
||||
- ArchivePage.tsx
|
||||
- Phase 3.1 — Human Review Preparation and Master Backlog Critique
|
||||
- blog/[slug]/page.tsx
|
||||
- compilerOptions
|
||||
@ -138,13 +138,13 @@
|
||||
- compilerOptions
|
||||
- backend/README.md
|
||||
- AdminService
|
||||
- UsersService
|
||||
- UsersController
|
||||
- Repository Map
|
||||
- validate_integrity.js
|
||||
- admin-panel/package.json
|
||||
- Sahel-Font
|
||||
- AdminController
|
||||
- AuthService
|
||||
- WikiService
|
||||
- Sahel-Font
|
||||
- Role & Core Objective
|
||||
- orchestrate.py
|
||||
@ -159,15 +159,15 @@
|
||||
- application/package.json
|
||||
- start-dev.js
|
||||
- generate-openapi.js
|
||||
- SafeImage.tsx
|
||||
- TorobController
|
||||
- PodcastPlayerModal.tsx
|
||||
- FaqService
|
||||
- System Discovery
|
||||
- HomeController
|
||||
- CreateUserDto
|
||||
- class-transformer
|
||||
- SmsSettingsPage.tsx
|
||||
- Product Requirement Document (PRD)
|
||||
- CreateReviewDto
|
||||
- MetricsController
|
||||
- helmet
|
||||
- RedisService
|
||||
- exclude
|
||||
- Baseline Command Plan & Reconciled Command History
|
||||
- seo-backfill.ts
|
||||
@ -198,8 +198,8 @@
|
||||
- ⚙️ Backend Technical Review (05_dev_backend)
|
||||
- 🎨 Frontend & Admin Panel Technical Review (06_dev_frontend)
|
||||
- 🚀 SEO & Content Strategy Review (12_seo_content)
|
||||
- UpdateReviewDto
|
||||
- app/page.tsx
|
||||
- js-yaml
|
||||
- @nestjs/core
|
||||
- seed-ui-texts.ts
|
||||
- seed-wiki.ts
|
||||
- update-blog.dto.ts
|
||||
@ -211,8 +211,8 @@
|
||||
- Raw Finding Verification & Disposition Report
|
||||
- React + TypeScript + Vite
|
||||
- Select.tsx
|
||||
- useSettingsStore
|
||||
- @types/node
|
||||
- userStore.ts
|
||||
- @nestjs/jwt
|
||||
- tailwindcss
|
||||
- application/README.md
|
||||
- deploy.sh
|
||||
@ -245,11 +245,15 @@
|
||||
- rules/graphify.md
|
||||
- .agents/workflows/graphify.md
|
||||
- instructions.md
|
||||
- track/page.tsx
|
||||
- typescript
|
||||
- @nestjs/swagger
|
||||
- @nestjs/throttler
|
||||
- tailwindcss
|
||||
- eslint-config-next
|
||||
- passport
|
||||
- reflect-metadata
|
||||
- swagger-ui-express
|
||||
- @eslint/eslintrc
|
||||
- @vitejs/plugin-react
|
||||
- eslint-plugin-prettier
|
||||
- supertest
|
||||
- blog.entity.ts
|
||||
- home.entity.ts
|
||||
@ -270,6 +274,11 @@
|
||||
- start.sh
|
||||
- User Login API
|
||||
- User Logout API
|
||||
- globals
|
||||
- jest
|
||||
- @nestjs/cli
|
||||
- @nestjs/testing
|
||||
- prettier
|
||||
- eslint-plugin-react-hooks
|
||||
- Canina Pharma GmbH
|
||||
- Pets Table
|
||||
@ -286,16 +295,19 @@
|
||||
- Staging Docker Compose
|
||||
- ZibalService
|
||||
- @nestjs/schematics
|
||||
- ts-jest
|
||||
- ZibalEBankService
|
||||
- .initiateOrderPayment
|
||||
- @tailwindcss/postcss
|
||||
- typescript
|
||||
- @types/js-yaml
|
||||
- typescript-eslint
|
||||
- prisma
|
||||
- source-map-support
|
||||
- @types/supertest
|
||||
- @eslint/js
|
||||
- ts-loader
|
||||
- ts-node
|
||||
- typescript-eslint
|
||||
- tsconfig-paths
|
||||
- @types/passport-jwt
|
||||
- 20260526160916_add_ui_texts_and_scientific_terms/migration.sql
|
||||
@ -304,6 +316,7 @@
|
||||
- @types/compression
|
||||
- revalidate/route.ts
|
||||
- MaskableField.tsx
|
||||
- @tailwindcss/postcss
|
||||
- @types/express
|
||||
- @types/jest
|
||||
- @types/react-dom
|
||||
@ -335,11 +348,11 @@
|
||||
backend/src/orders/orders.controller.ts → frontend/admin-panel/src/types/admin.ts
|
||||
|
||||
## Import Cycles
|
||||
- 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`
|
||||
- 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`
|
||||
- 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`
|
||||
|
||||
## Communities (314 total, 100 thin omitted)
|
||||
## Communities (327 total, 116 thin omitted)
|
||||
|
||||
### Community 0 - "Roles"
|
||||
Cohesion: 0.24
|
||||
@ -347,7 +360,7 @@ Nodes (13): Roles(), PaymentController, ApiBearerAuth, ApiOperation, ApiTags, Bo
|
||||
|
||||
### Community 1 - "app.module.ts"
|
||||
Cohesion: 0.06
|
||||
Nodes (40): B2BModule, Module, BannersModule, Module, CmsModule, Module, SmsModule, Global (+32 more)
|
||||
Nodes (39): B2BModule, Module, BannersModule, Module, CmsModule, Module, RevalidationModule, Global (+31 more)
|
||||
|
||||
### Community 2 - "PaymentService"
|
||||
Cohesion: 0.11
|
||||
@ -355,11 +368,11 @@ Nodes (9): AdminTransactionFilterDto, ApiPropertyOptional, IsIn, IsNumber, IsOpt
|
||||
|
||||
### Community 3 - "productService.ts"
|
||||
Cohesion: 0.06
|
||||
Nodes (36): dynamic, GET(), revalidate, dynamic, GET(), revalidate, DEFAULT_CATEGORIES, dynamic (+28 more)
|
||||
Nodes (41): buildTorobProductSpec(), buildTorobProductTitle(), dynamic, GET(), revalidate, dynamic, GET(), revalidate (+33 more)
|
||||
|
||||
### Community 4 - "PetProfile.tsx"
|
||||
### Community 4 - "UserDashboard.tsx"
|
||||
Cohesion: 0.11
|
||||
Nodes (19): metadata, FeaturedProducts(), ProductCard(), OrderSuccess(), PrescriptionUploadModal(), PrescriptionUploadModalProps, SearchResultsPage(), CURRENT_SYMPTOMS (+11 more)
|
||||
Nodes (20): metadata, OrderDetailsModal(), PetProfile(), SearchResultsPage(), CURRENT_SYMPTOMS, MEDICAL_HISTORIES, SmartAdvisor(), SmartAdvisorProps (+12 more)
|
||||
|
||||
### Community 5 - "CmsController"
|
||||
Cohesion: 0.09
|
||||
@ -367,7 +380,7 @@ Nodes (24): CmsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controlle
|
||||
|
||||
### Community 6 - "tickets.controller.ts"
|
||||
Cohesion: 0.09
|
||||
Nodes (29): AdminUpdateTicketDto, CreateTicketDto, ReplyTicketDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString (+21 more)
|
||||
Nodes (31): AdminUpdateTicketDto, CreateTicketDto, ReplyTicketDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString (+23 more)
|
||||
|
||||
### Community 7 - "Button.tsx"
|
||||
Cohesion: 0.12
|
||||
@ -378,12 +391,12 @@ Cohesion: 0.13
|
||||
Nodes (13): ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiTags, Controller, Get, Param, Query (+5 more)
|
||||
|
||||
### Community 9 - "devDependencies"
|
||||
Cohesion: 0.08
|
||||
Nodes (25): devDependencies, @eslint/eslintrc, eslint-plugin-prettier, globals, jest, @nestjs/cli, @nestjs/testing, prettier (+17 more)
|
||||
Cohesion: 0.22
|
||||
Nodes (9): devDependencies, ts-node, @types/bcryptjs, @types/node, typescript, @types/node, typescript, ts-node (+1 more)
|
||||
|
||||
### Community 10 - "ReviewsService"
|
||||
Cohesion: 0.12
|
||||
Nodes (17): ReviewsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+9 more)
|
||||
### Community 10 - "CreateReviewDto"
|
||||
Cohesion: 0.07
|
||||
Nodes (30): CreateReviewDto, ApiProperty, IsInt, IsNotEmpty, IsOptional, IsString, Min, ApiProperty (+22 more)
|
||||
|
||||
### Community 11 - "MediaSelector.tsx"
|
||||
Cohesion: 0.06
|
||||
@ -398,8 +411,8 @@ Cohesion: 0.06
|
||||
Nodes (28): AppModule, Module, EnvironmentVariables, IsNotEmpty, IsOptional, IsString, validateEnv(), CustomHttpExceptionFilter (+20 more)
|
||||
|
||||
### Community 14 - "toPersian"
|
||||
Cohesion: 0.09
|
||||
Nodes (41): VerifyContent(), AddressFormData, AddressModal(), AddressModalProps, normalizeDigits(), validateAddressForm(), ArchiveProductCard(), AuthModal() (+33 more)
|
||||
Cohesion: 0.17
|
||||
Nodes (19): VerifyContent(), AddressFormData, AddressModal(), AddressModalProps, normalizeDigits(), validateAddressForm(), BackButton(), BackButtonProps (+11 more)
|
||||
|
||||
### Community 15 - "src/services/api.ts"
|
||||
Cohesion: 0.08
|
||||
@ -414,20 +427,20 @@ Cohesion: 0.15
|
||||
Nodes (18): B2BPage(), generateMetadata(), ContactPage(), generateMetadata(), generateMetadata(), getRelatedProducts(), getWikiTerm(), WikiTermPage() (+10 more)
|
||||
|
||||
### Community 18 - "JwtAuthGuard"
|
||||
Cohesion: 0.17
|
||||
Cohesion: 0.18
|
||||
Nodes (7): JwtAuthGuard, Injectable, ROLES_KEY, RequestWithUser, RolesGuard, Injectable, UserReqPayload
|
||||
|
||||
### Community 19 - "ProductsService"
|
||||
Cohesion: 0.18
|
||||
Nodes (10): GetProductsDto, ApiPropertyOptional, IsEnum, IsOptional, IsString, Query, ProductsModule, Module (+2 more)
|
||||
### Community 19 - "users.controller.ts"
|
||||
Cohesion: 0.13
|
||||
Nodes (13): AddressDto, ApiProperty, ApiPropertyOptional, IsBoolean, IsNotEmpty, IsOptional, IsString, ApiPropertyOptional (+5 more)
|
||||
|
||||
### Community 20 - "CreateVideoDto"
|
||||
Cohesion: 0.09
|
||||
Nodes (24): CreateVideoDto, ApiProperty, ApiPropertyOptional, IsBoolean, IsNotEmpty, IsOptional, IsString, UpdateVideoDto (+16 more)
|
||||
|
||||
### Community 21 - "admin.module.ts"
|
||||
Cohesion: 0.07
|
||||
Nodes (22): AdminModule, Module, MediaService, Injectable, ReportsController, ApiBearerAuth, ApiOperation, ApiQuery (+14 more)
|
||||
### Community 21 - "ReportsController"
|
||||
Cohesion: 0.14
|
||||
Nodes (11): ReportsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Get, Query (+3 more)
|
||||
|
||||
### Community 22 - "SmsService"
|
||||
Cohesion: 0.06
|
||||
@ -470,24 +483,24 @@ Cohesion: 0.06
|
||||
Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, Category, Completion Statement, Confidence, Dependencies (+23 more)
|
||||
|
||||
### Community 32 - "adminRoutes.tsx"
|
||||
Cohesion: 0.06
|
||||
Nodes (24): App(), Props, RouteErrorBoundary, State, HeroBanner, VetTestimonial, BestSellerItem, CategoryDistItem (+16 more)
|
||||
Cohesion: 0.08
|
||||
Nodes (17): App(), Props, RouteErrorBoundary, State, HeroBanner, VetTestimonial, AdminRouteConfig, CMS (+9 more)
|
||||
|
||||
### Community 33 - "WholesaleApplyDto"
|
||||
Cohesion: 0.10
|
||||
Nodes (20): ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, WholesaleApplyDto, ApiBearerAuth, ApiOperation (+12 more)
|
||||
### Community 33 - "ContactService"
|
||||
Cohesion: 0.06
|
||||
Nodes (32): ContactController, Body, Controller, Get, Param, Post, Put, Query (+24 more)
|
||||
|
||||
### Community 34 - "B2BService"
|
||||
Cohesion: 0.13
|
||||
Nodes (15): B2BController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param (+7 more)
|
||||
|
||||
### Community 35 - "AuthController"
|
||||
Cohesion: 0.23
|
||||
Cohesion: 0.25
|
||||
Nodes (13): AuthController, ApiBadRequestResponse, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+5 more)
|
||||
|
||||
### Community 36 - "FaqService"
|
||||
### Community 36 - "FaqController"
|
||||
Cohesion: 0.14
|
||||
Nodes (14): FaqController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
|
||||
Nodes (12): FaqController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+4 more)
|
||||
|
||||
### Community 37 - "راهنمای تست سیستم (Software Testing)"
|
||||
Cohesion: 0.07
|
||||
@ -501,9 +514,9 @@ Nodes (19): TransactionReceiptData, TransactionReceiptModal(), TransactionReceip
|
||||
Cohesion: 0.10
|
||||
Nodes (16): CategoriesController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+8 more)
|
||||
|
||||
### Community 40 - "MediaController"
|
||||
Cohesion: 0.11
|
||||
Nodes (14): MediaController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
|
||||
### Community 40 - "admin.module.ts"
|
||||
Cohesion: 0.09
|
||||
Nodes (19): AdminModule, Module, MediaController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller (+11 more)
|
||||
|
||||
### Community 41 - "What You Must Do When Invoked"
|
||||
Cohesion: 0.07
|
||||
@ -543,7 +556,7 @@ Nodes (19): autoprefixer, devDependencies, autoprefixer, eslint, @eslint/js, glo
|
||||
|
||||
### Community 50 - "devDependencies"
|
||||
Cohesion: 0.12
|
||||
Nodes (17): devDependencies, eslint, jsdom, @tailwindcss/postcss, @testing-library/jest-dom, @testing-library/react, @types/node, @types/react (+9 more)
|
||||
Nodes (17): eslint-config-next, devDependencies, eslint, eslint-config-next, jsdom, @testing-library/jest-dom, @testing-library/react, @types/node (+9 more)
|
||||
|
||||
### Community 51 - "BlogsController"
|
||||
Cohesion: 0.14
|
||||
@ -573,45 +586,45 @@ Nodes (15): Skeleton(), MonitoringStats, getPaymentMethodLabel(), Order, ORDER_S
|
||||
Cohesion: 0.09
|
||||
Nodes (22): CREATE MODE — Normal Operation, Expected JSON Output Schema, File Scope Boundary, Forbidden Actions, MODE 1: REVIEW (called during Review Phase), MODE 2: CONTENT CREATION (standalone task from backlog), Operating Modes, Operational Rules & Boundaries (+14 more)
|
||||
|
||||
### Community 58 - "ContactService"
|
||||
Cohesion: 0.13
|
||||
Nodes (12): ContactController, Body, Controller, Get, Param, Post, Put, Query (+4 more)
|
||||
### Community 58 - "auth.module.ts"
|
||||
Cohesion: 0.17
|
||||
Nodes (10): DEFAULT_JWT_REFRESH_SECRET, DEFAULT_JWT_SECRET, getJwtSecret(), AuthModule, Module, JwtPayload, JwtStrategy, Injectable (+2 more)
|
||||
|
||||
### Community 59 - "compilerOptions"
|
||||
Cohesion: 0.06
|
||||
Nodes (30): compilerOptions, allowSyntheticDefaultImports, baseUrl, declaration, emitDecoratorMetadata, esModuleInterop, experimentalDecorators, forceConsistentCasingInFileNames (+22 more)
|
||||
|
||||
### Community 60 - "admin.service.ts"
|
||||
Cohesion: 0.10
|
||||
Nodes (12): CouponTargetInput, PaginationQuery, ProductDto, ApiPropertyOptional, IsArray, IsBoolean, IsNumber, IsOptional (+4 more)
|
||||
Cohesion: 0.13
|
||||
Nodes (21): CouponInput, CouponTargetInput, PaginationQuery, ProductDto, ApiPropertyOptional, IsArray, IsBoolean, IsNumber (+13 more)
|
||||
|
||||
### Community 61 - "ProductPage.tsx"
|
||||
Cohesion: 0.10
|
||||
Cohesion: 0.09
|
||||
Nodes (20): ProductImageZoomModalProps, CalculatorState, GalleryMediaItem, ICON_MAP, isVideoUrl(), ProductImageZoomModal, ProductPage(), ProductReviews (+12 more)
|
||||
|
||||
### Community 62 - "RevalidationService"
|
||||
Cohesion: 0.16
|
||||
Nodes (5): RevalidationModule, Global, Module, RevalidationService, Injectable
|
||||
|
||||
### Community 63 - "dependencies"
|
||||
Cohesion: 0.05
|
||||
Nodes (43): dependencies, bcrypt, bcryptjs, class-transformer, class-validator, compression, helmet, ioredis (+35 more)
|
||||
Cohesion: 0.09
|
||||
Nodes (23): dependencies, bcrypt, bcryptjs, class-validator, compression, ioredis, @nestjs/common, @nestjs/passport (+15 more)
|
||||
|
||||
### Community 64 - "compilerOptions"
|
||||
Cohesion: 0.10
|
||||
Nodes (20): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, lib, module, moduleDetection, moduleResolution, noEmit (+12 more)
|
||||
|
||||
### Community 66 - "AdminQueryDto"
|
||||
Cohesion: 0.18
|
||||
Nodes (7): ApiQuery, Get, Query, AdminQueryDto, ApiPropertyOptional, IsOptional, IsString
|
||||
### Community 65 - "BlogsService"
|
||||
Cohesion: 0.09
|
||||
Nodes (4): BlogsService, Injectable, RevalidationService, Injectable
|
||||
|
||||
### Community 66 - "ApiOperation"
|
||||
Cohesion: 0.13
|
||||
Nodes (8): ApiOperation, ApiQuery, Get, Query, AdminQueryDto, ApiPropertyOptional, IsOptional, IsString
|
||||
|
||||
### Community 67 - "PetsController"
|
||||
Cohesion: 0.10
|
||||
Nodes (14): PetsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Delete, Get (+6 more)
|
||||
|
||||
### Community 68 - "lib/services/api.ts"
|
||||
Cohesion: 0.09
|
||||
Nodes (19): BlogPost, ContactInfoItem, FAQItem, OrderDetailsModalProps, Testimonial, api, ApiErrorPayload, BASE_DOMAIN (+11 more)
|
||||
### Community 68 - "useSettingsStore"
|
||||
Cohesion: 0.08
|
||||
Nodes (36): HomeClient(), HomeClientProps, B2BLandingClient(), BlogPostClientProps, BlogPost, BlogPreviewSection(), ContactInfoItem, EnamadBadge() (+28 more)
|
||||
|
||||
### Community 69 - "Required Review Group Closures"
|
||||
Cohesion: 0.10
|
||||
@ -622,8 +635,8 @@ Cohesion: 0.08
|
||||
Nodes (23): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, jsx, lib, module, moduleDetection, moduleResolution (+15 more)
|
||||
|
||||
### Community 71 - "getPageMetadata"
|
||||
Cohesion: 0.10
|
||||
Nodes (13): generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata() (+5 more)
|
||||
Cohesion: 0.08
|
||||
Nodes (17): generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata(), getHomeData(), Home(), generateMetadata(), generateMetadata() (+9 more)
|
||||
|
||||
### Community 72 - "Operational Rules & Boundaries"
|
||||
Cohesion: 0.11
|
||||
@ -638,12 +651,12 @@ Cohesion: 0.13
|
||||
Nodes (14): ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete, Get (+6 more)
|
||||
|
||||
### Community 75 - "PetsController"
|
||||
Cohesion: 0.08
|
||||
Nodes (32): CreateHealthLogDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, CreateReminderDto, ApiProperty (+24 more)
|
||||
Cohesion: 0.05
|
||||
Nodes (47): CreateHealthLogDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, CreatePetDto, ApiProperty (+39 more)
|
||||
|
||||
### Community 76 - "ProductsController"
|
||||
Cohesion: 0.15
|
||||
Nodes (9): ProductsController, ApiOperation, ApiTags, Body, Controller, Get, Param, Patch (+1 more)
|
||||
### Community 76 - "ProductsService"
|
||||
Cohesion: 0.07
|
||||
Nodes (27): GetProductsDto, ApiPropertyOptional, IsEnum, IsOptional, IsString, ProductsController, ApiOperation, ApiTags (+19 more)
|
||||
|
||||
### Community 77 - "seo.module.ts"
|
||||
Cohesion: 0.16
|
||||
@ -677,9 +690,9 @@ Nodes (21): dependencies, axios, lucide-react, react, react-dom, react-hot-toast
|
||||
Cohesion: 0.12
|
||||
Nodes (16): 1. Active Secret Scanning (All Modified Files), 2. Docker Container Verification (if Dockerfile exists), 3. CI/CD Basic Check (if `.github/workflows/` exists), 4. Failure Routing Protocol, 5. Forbidden Actions, ENFORCE MODE — Normal Operation, Expected JSON Output Schema, Operational Rules & Boundaries (+8 more)
|
||||
|
||||
### Community 85 - "pets/pets.controller.ts"
|
||||
Cohesion: 0.11
|
||||
Nodes (15): CreatePetDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional, IsString (+7 more)
|
||||
### Community 85 - "Reports.tsx"
|
||||
Cohesion: 0.20
|
||||
Nodes (7): BestSellerItem, CategoryDistItem, COLORS, CustomTooltipProps, ReportData, TopCouponItem, Reports
|
||||
|
||||
### Community 86 - "zibal.service.ts"
|
||||
Cohesion: 0.16
|
||||
@ -697,21 +710,21 @@ Nodes (8): CreateEBankCheckoutDto, ApiProperty, ApiPropertyOptional, IsNotEmpty,
|
||||
Cohesion: 0.17
|
||||
Nodes (12): prisma, main(), prisma, determineSuitableFor(), findOrCreateCategory(), getProductImageUrl(), main(), parseSize() (+4 more)
|
||||
|
||||
### Community 90 - "UserDashboard.tsx"
|
||||
Cohesion: 0.12
|
||||
Nodes (11): DeleteConfirmModal(), DeleteConfirmModalProps, OrderDetailsModal(), OrderRowSkeleton(), PetProfileSkeleton(), Skeleton(), SkeletonProps, CreateTicketPayload (+3 more)
|
||||
### Community 90 - "useCartStore"
|
||||
Cohesion: 0.09
|
||||
Nodes (19): B2BPortal, CartDrawer, B2BPortal(), CartDrawer(), DeleteConfirmModal(), DeleteConfirmModalProps, OrderSuccess(), OrderTracking() (+11 more)
|
||||
|
||||
### Community 91 - "Reconciled Audit Roles & Assignments"
|
||||
Cohesion: 0.12
|
||||
Nodes (15): 10. Documentation Engineer, 1. Lead Architect / Orchestrator, 2. React / Vite Storefront Auditor, 3. NestJS Backend Auditor, 4. Admin Features Auditor, 5. Database & Data Integrity Auditor, 6. Security Auditor, 7. TypeScript & Code Quality Auditor (+7 more)
|
||||
|
||||
### Community 92 - "OrdersService"
|
||||
Cohesion: 0.07
|
||||
Nodes (29): CreateOrderDto, OrderItemDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional (+21 more)
|
||||
Cohesion: 0.06
|
||||
Nodes (35): CreateOrderDto, OrderItemDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional (+27 more)
|
||||
|
||||
### Community 93 - "HomeClient.tsx"
|
||||
Cohesion: 0.09
|
||||
Nodes (22): HomeClient(), HomeClientProps, ArchivePage(), CATEGORY_MAP, ICON_MAP, BannerPlacement(), BannerPlacementProps, BlogPreviewSection() (+14 more)
|
||||
### Community 93 - "ArchivePage.tsx"
|
||||
Cohesion: 0.08
|
||||
Nodes (21): ArchivePage(), ArchiveProductCard(), CATEGORY_MAP, ICON_MAP, BannerPlacement(), BannerPlacementProps, OrderRowSkeleton(), PetProfileSkeleton() (+13 more)
|
||||
|
||||
### Community 94 - "Phase 3.1 — Human Review Preparation and Master Backlog Critique"
|
||||
Cohesion: 0.13
|
||||
@ -762,16 +775,16 @@ Cohesion: 0.17
|
||||
Nodes (11): 1. Detect Test Runner from Tech Stack, 2. Execution Output Capture (Mandatory), 3. Acceptance Criteria Verification, 4. Failure Routing Protocol, 5. Success Routing Protocol, 6. Forbidden Actions, Expected JSON Output Schema, Operational Rules & Boundaries (+3 more)
|
||||
|
||||
### Community 106 - "AuthService"
|
||||
Cohesion: 0.13
|
||||
Nodes (3): AuthService, Injectable, normalizeMobile()
|
||||
Cohesion: 0.18
|
||||
Nodes (4): AuthService, Injectable, normalizeMobile(), UserAddressInput
|
||||
|
||||
### Community 107 - "PaginationDto"
|
||||
Cohesion: 0.06
|
||||
Nodes (25): BlogsModule, Module, BlogFilterDto, BlogsService, Injectable, PaginationDto, SortOrder, ApiPropertyOptional (+17 more)
|
||||
Cohesion: 0.07
|
||||
Nodes (20): CategoryQuery, BlogsModule, Module, BlogFilterDto, BlogsService, Injectable, PaginationDto, SortOrder (+12 more)
|
||||
|
||||
### Community 108 - "PrismaService"
|
||||
Cohesion: 0.07
|
||||
Nodes (20): CategoryQuery, B2BWholesaleOrderItem, MeliPayamakPattern, MeliPayamakResponse, SendPatternSmsOptions, SmsConfig, SmsLogQuery, CreateContactSubmissionDto (+12 more)
|
||||
Nodes (20): WikiQuery, B2BWholesaleOrderItem, MeliPayamakPattern, MeliPayamakResponse, SendPatternSmsOptions, SmsConfig, SmsLogQuery, CreateContactSubmissionDto (+12 more)
|
||||
|
||||
### Community 109 - "1. Summary of Integrity Repairs Performed"
|
||||
Cohesion: 0.17
|
||||
@ -825,9 +838,13 @@ Nodes (9): compilerOptions, esModuleInterop, module, moduleResolution, skipLibCh
|
||||
Cohesion: 0.20
|
||||
Nodes (9): Compile and run the project, Deployment, Description, License, Project setup, Resources, Run tests, Stay in touch (+1 more)
|
||||
|
||||
### Community 123 - "UsersService"
|
||||
Cohesion: 0.06
|
||||
Nodes (42): DEFAULT_JWT_REFRESH_SECRET, DEFAULT_JWT_SECRET, getJwtSecret(), AuthModule, Module, JwtPayload, JwtStrategy, Injectable (+34 more)
|
||||
### Community 122 - "AdminService"
|
||||
Cohesion: 0.15
|
||||
Nodes (4): Body, Post, AdminService, Injectable
|
||||
|
||||
### Community 123 - "UsersController"
|
||||
Cohesion: 0.20
|
||||
Nodes (16): ApiBadRequestResponse, ApiBearerAuth, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+8 more)
|
||||
|
||||
### Community 124 - "Repository Map"
|
||||
Cohesion: 0.20
|
||||
@ -846,8 +863,8 @@ Cohesion: 0.20
|
||||
Nodes (9): Arch Linux, Contributors, Install, Known problems for variable version, License, Sahel-Font, To Do (variable), طریقه استفاده از نسخه متغیر variable (+1 more)
|
||||
|
||||
### Community 128 - "AdminController"
|
||||
Cohesion: 0.19
|
||||
Nodes (12): AdminController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Param (+4 more)
|
||||
Cohesion: 0.11
|
||||
Nodes (8): AdminController, ApiBearerAuth, ApiTags, Controller, Delete, Param, Put, UseGuards
|
||||
|
||||
### Community 130 - "Sahel-Font"
|
||||
Cohesion: 0.20
|
||||
@ -905,13 +922,13 @@ Nodes (7): backend, distMainPath, fs, http, path, { spawn, execSync }, waitForBa
|
||||
Cohesion: 0.25
|
||||
Nodes (6): app_module_1, core_1, fs, path, swagger_1, yaml
|
||||
|
||||
### Community 144 - "SafeImage.tsx"
|
||||
Cohesion: 0.10
|
||||
Nodes (20): BackButton(), BackButtonProps, BlogPostClientProps, PLAYBACK_RATES, PodcastInlinePlayer(), PodcastInlinePlayerProps, PLAYBACK_RATES, PodcastPlayerModal() (+12 more)
|
||||
### Community 144 - "PodcastPlayerModal.tsx"
|
||||
Cohesion: 0.40
|
||||
Nodes (3): PLAYBACK_RATES, PodcastPlayerModal(), PodcastPlayerModalProps
|
||||
|
||||
### Community 145 - "TorobController"
|
||||
Cohesion: 0.25
|
||||
Nodes (6): TorobController, ApiOperation, ApiTags, Controller, Get, Query
|
||||
### Community 145 - "FaqService"
|
||||
Cohesion: 0.33
|
||||
Nodes (4): FaqModule, Module, FaqService, Injectable
|
||||
|
||||
### Community 146 - "System Discovery"
|
||||
Cohesion: 0.25
|
||||
@ -921,10 +938,6 @@ Nodes (7): Active Core Applications, Current Architecture, Evidence-Based Status
|
||||
Cohesion: 0.16
|
||||
Nodes (10): HomeController, ApiOkResponse, ApiOperation, ApiTags, Controller, Get, HomeModule, Module (+2 more)
|
||||
|
||||
### Community 148 - "CreateUserDto"
|
||||
Cohesion: 0.29
|
||||
Nodes (10): CreateUserDto, ApiProperty, ApiPropertyOptional, IsEmail, IsNotEmpty, IsNumber, IsOptional, IsString (+2 more)
|
||||
|
||||
### Community 149 - "SmsSettingsPage.tsx"
|
||||
Cohesion: 0.29
|
||||
Nodes (7): PatternItem, SmsConfigState, SmsLogItem, SmsLogStats, SmsSettingsPage(), toPersianDigits(), SmsSettingsPage
|
||||
@ -933,13 +946,9 @@ Nodes (7): PatternItem, SmsConfigState, SmsLogItem, SmsLogStats, SmsSettingsPage
|
||||
Cohesion: 0.29
|
||||
Nodes (6): 1. Executive Vision, 2. Target Audience, 3. Functional Requirements, 4. Non-Functional Requirements (Performance, Security), 5. Epic / Feature Breakdown, Product Requirement Document (PRD)
|
||||
|
||||
### Community 151 - "CreateReviewDto"
|
||||
Cohesion: 0.25
|
||||
Nodes (8): CreateReviewDto, ApiProperty, IsInt, IsNotEmpty, IsOptional, IsString, Min, Max
|
||||
|
||||
### Community 152 - "MetricsController"
|
||||
Cohesion: 0.29
|
||||
Nodes (5): ApiExcludeController, MetricsController, Controller, Get, Res
|
||||
### Community 152 - "RedisService"
|
||||
Cohesion: 0.10
|
||||
Nodes (10): ApiExcludeController, MetricsController, Controller, Get, Res, RedisModule, Global, Module (+2 more)
|
||||
|
||||
### Community 153 - "exclude"
|
||||
Cohesion: 0.22
|
||||
@ -1057,14 +1066,6 @@ Nodes (3): Architectural Overview, Critical Review Findings & Required Enhanceme
|
||||
Cohesion: 0.50
|
||||
Nodes (3): Overview & Content Foundation, SEO & Content Requirements, 🚀 SEO & Content Strategy Review (12_seo_content)
|
||||
|
||||
### Community 184 - "UpdateReviewDto"
|
||||
Cohesion: 0.40
|
||||
Nodes (5): ApiProperty, IsIn, IsOptional, IsString, UpdateReviewDto
|
||||
|
||||
### Community 185 - "app/page.tsx"
|
||||
Cohesion: 0.67
|
||||
Nodes (3): generateMetadata(), getHomeData(), Home()
|
||||
|
||||
### Community 191 - "graphify reference: add a URL and watch a folder"
|
||||
Cohesion: 0.50
|
||||
Nodes (3): For /graphify add, For --watch, graphify reference: add a URL and watch a folder
|
||||
@ -1089,9 +1090,9 @@ Nodes (3): Expanding the ESLint configuration, React Compiler, React + TypeScrip
|
||||
Cohesion: 0.50
|
||||
Nodes (3): Select, SelectOption, SelectProps
|
||||
|
||||
### Community 197 - "useSettingsStore"
|
||||
Cohesion: 0.11
|
||||
Nodes (20): AuthModal, B2BPortal, CartDrawer, ClientLayout(), LoginModal, B2BLandingClient(), BrandLogo(), BrandLogoProps (+12 more)
|
||||
### Community 197 - "userStore.ts"
|
||||
Cohesion: 0.06
|
||||
Nodes (33): AuthModal, ClientLayout(), LoginModal, AuthModal(), AuthModalProps, extractOtpFromText(), BrandLogo(), BrandLogoProps (+25 more)
|
||||
|
||||
### Community 200 - "application/README.md"
|
||||
Cohesion: 0.50
|
||||
@ -1120,22 +1121,22 @@ Nodes (3): GET(), handleRevalidate(), POST()
|
||||
## Knowledge Gaps
|
||||
- **1346 isolated node(s):** `$schema`, `collection`, `sourceRoot`, `deleteOutDir`, `name` (+1341 more)
|
||||
These have ≤1 connection - possible missing edges or undocumented components.
|
||||
- **100 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
|
||||
- **116 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
|
||||
|
||||
## Suggested Questions
|
||||
_Questions this graph is uniquely positioned to answer:_
|
||||
|
||||
- **Why does `ApiResponse` connect `src/services/api.ts` to `BlogsController`, `AuthController`, `WikiController`, `PetsController`, `ProductsController`, `HomeController`, `UsersService`, `OrdersService`?**
|
||||
_High betweenness centrality (0.084) - this node is a cross-community bridge._
|
||||
- **Why does `Roles()` connect `Roles` to `WholesaleApplyDto`, `B2BService`, `FaqService`, `CmsController`, `tickets.controller.ts`, `SslController`, `BannersService`, `ProductsController`, `ReviewsService`, `TestimonialsService`, `IngredientsService`, `JwtAuthGuard`, `ProductsService`, `PrescriptionsService`, `SmartAdvisorService`, `SmsService`, `MenuService`, `ContactService`?**
|
||||
_High betweenness centrality (0.064) - this node is a cross-community bridge._
|
||||
- **Why does `JwtAuthGuard` connect `JwtAuthGuard` to `PetsController`, `CmsController`, `tickets.controller.ts`, `PaginationDto`, `auth.service.ts`, `DoctorQueryDto`, `ProductsService`, `admin.module.ts`, `pets/pets.controller.ts`, `UsersService`?**
|
||||
_High betweenness centrality (0.035) - this node is a cross-community bridge._
|
||||
- **Why does `ApiResponse` connect `src/services/api.ts` to `BlogsController`, `AuthController`, `WikiController`, `PetsController`, `ProductsService`, `HomeController`, `UsersController`, `OrdersService`?**
|
||||
_High betweenness centrality (0.081) - this node is a cross-community bridge._
|
||||
- **Why does `Roles()` connect `Roles` to `ContactService`, `B2BService`, `FaqController`, `CmsController`, `tickets.controller.ts`, `SslController`, `BannersService`, `ProductsService`, `CreateReviewDto`, `TestimonialsService`, `IngredientsService`, `JwtAuthGuard`, `PrescriptionsService`, `SmartAdvisorService`, `SmsService`, `MenuService`?**
|
||||
_High betweenness centrality (0.065) - this node is a cross-community bridge._
|
||||
- **Why does `JwtAuthGuard` connect `JwtAuthGuard` to `WikiService`, `PetsController`, `CmsController`, `tickets.controller.ts`, `admin.module.ts`, `PaginationDto`, `PetsController`, `ProductsService`, `OrdersService`, `auth.service.ts`, `DoctorQueryDto`, `users.controller.ts`, `ReportsController`, `admin.service.ts`?**
|
||||
_High betweenness centrality (0.034) - this node is a cross-community bridge._
|
||||
- **What connects `$schema`, `collection`, `sourceRoot` to the rest of the system?**
|
||||
_1346 weakly-connected nodes found - possible documentation gaps or missing edges._
|
||||
- **Should `app.module.ts` be split into smaller, more focused modules?**
|
||||
_Cohesion score 0.061016949152542375 - nodes in this community are weakly interconnected._
|
||||
_Cohesion score 0.062310949788263764 - nodes in this community are weakly interconnected._
|
||||
- **Should `PaymentService` be split into smaller, more focused modules?**
|
||||
_Cohesion score 0.11 - nodes in this community are weakly interconnected._
|
||||
- **Should `productService.ts` be split into smaller, more focused modules?**
|
||||
_Cohesion score 0.059907834101382486 - nodes in this community are weakly interconnected._
|
||||
_Cohesion score 0.055130784708249496 - nodes in this community are weakly interconnected._
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,16 @@
|
||||
# Graph Report - canina (2026-09-02)
|
||||
|
||||
## Corpus Check
|
||||
- 596 files · ~1,072,182 words
|
||||
- 599 files · ~1,076,804 words
|
||||
- Verdict: corpus is large enough that graph structure adds value.
|
||||
|
||||
## Summary
|
||||
- 4173 nodes · 7530 edges · 327 communities (211 shown, 116 thin omitted)
|
||||
- Extraction: 96% EXTRACTED · 4% INFERRED · 0% AMBIGUOUS · INFERRED: 281 edges (avg confidence: 0.79)
|
||||
- 4207 nodes · 7629 edges · 300 communities (207 shown, 93 thin omitted)
|
||||
- Extraction: 96% EXTRACTED · 4% INFERRED · 0% AMBIGUOUS · INFERRED: 287 edges (avg confidence: 0.79)
|
||||
- Token cost: 0 input · 0 output
|
||||
|
||||
## Graph Freshness
|
||||
- Built from commit: `88060431`
|
||||
- Built from commit: `382ebc0c`
|
||||
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
|
||||
- Run `graphify update .` after code changes (no API cost).
|
||||
|
||||
@ -19,14 +19,14 @@
|
||||
- app.module.ts
|
||||
- PaymentService
|
||||
- productService.ts
|
||||
- UserDashboard.tsx
|
||||
- PetProfile.tsx
|
||||
- CmsController
|
||||
- tickets.controller.ts
|
||||
- Button.tsx
|
||||
- WikiController
|
||||
- devDependencies
|
||||
- CreateReviewDto
|
||||
- MediaSelector.tsx
|
||||
- ConfirmModal.tsx
|
||||
- index.ts
|
||||
- app-audit-verification.e2e-spec.js
|
||||
- toPersian
|
||||
@ -34,7 +34,7 @@
|
||||
- DoctorQueryDto
|
||||
- schema.ts
|
||||
- JwtAuthGuard
|
||||
- users.controller.ts
|
||||
- admin.service.ts
|
||||
- CreateVideoDto
|
||||
- ReportsController
|
||||
- SmsService
|
||||
@ -48,14 +48,14 @@
|
||||
- DEVOPS-001
|
||||
- DOC-001
|
||||
- adminRoutes.tsx
|
||||
- ContactService
|
||||
- WholesaleApplyDto
|
||||
- B2BService
|
||||
- AuthController
|
||||
- FaqController
|
||||
- ContactService
|
||||
- FaqService
|
||||
- راهنمای تست سیستم (Software Testing)
|
||||
- Transactions.tsx
|
||||
- Button
|
||||
- CategoriesController
|
||||
- admin.module.ts
|
||||
- MediaController
|
||||
- What You Must Do When Invoked
|
||||
- SslController
|
||||
- BannersService
|
||||
@ -67,21 +67,21 @@
|
||||
- devDependencies
|
||||
- devDependencies
|
||||
- BlogsController
|
||||
- PrescriptionsService
|
||||
- prescriptions.controller.ts
|
||||
- SmartAdvisorService
|
||||
- Modal.tsx
|
||||
- UITexts.tsx
|
||||
- Orders.tsx
|
||||
- PrescriptionsManager.tsx
|
||||
- Role & Core Objective
|
||||
- auth.module.ts
|
||||
- UserDashboard.tsx
|
||||
- compilerOptions
|
||||
- admin.service.ts
|
||||
- CreateUserDto
|
||||
- ProductPage.tsx
|
||||
- UsersService
|
||||
- BlogsService
|
||||
- dependencies
|
||||
- compilerOptions
|
||||
- BlogsService
|
||||
- ApiOperation
|
||||
- RevalidationService
|
||||
- Get
|
||||
- PetsController
|
||||
- useSettingsStore
|
||||
- Required Review Group Closures
|
||||
@ -100,17 +100,17 @@
|
||||
- scripts
|
||||
- dependencies
|
||||
- Role & Core Objective
|
||||
- Reports.tsx
|
||||
- torob.controller.ts
|
||||
- zibal.service.ts
|
||||
- dependencies
|
||||
- CreateEBankCheckoutDto
|
||||
- seed-products.ts
|
||||
- useCartStore
|
||||
- OrderService
|
||||
- Reconciled Audit Roles & Assignments
|
||||
- OrdersService
|
||||
- ArchivePage.tsx
|
||||
- Phase 3.1 — Human Review Preparation and Master Backlog Critique
|
||||
- blog/[slug]/page.tsx
|
||||
- wiki/[slug]/page.tsx
|
||||
- compilerOptions
|
||||
- InitiatePaymentDto
|
||||
- scripts
|
||||
@ -119,18 +119,18 @@
|
||||
- Operational Rules & Boundaries
|
||||
- jest
|
||||
- Comprehensive Change Log
|
||||
- Coupons.tsx
|
||||
- Products.tsx
|
||||
- Operational Rules & Boundaries
|
||||
- AuthService
|
||||
- RedisService
|
||||
- PaginationDto
|
||||
- PrismaService
|
||||
- 1. Summary of Integrity Repairs Performed
|
||||
- Operational Rules & Boundaries
|
||||
- Operational Rules & Boundaries
|
||||
- Operational Rules & Boundaries
|
||||
- RegisterDto
|
||||
- Orders.tsx
|
||||
- AppService
|
||||
- Blogs.tsx
|
||||
- MediaSelector.tsx
|
||||
- Vazirmatn Changelog
|
||||
- Vazirmatn Font فونت وزیرمتن
|
||||
- Operational Rules & Boundaries
|
||||
@ -138,7 +138,7 @@
|
||||
- compilerOptions
|
||||
- backend/README.md
|
||||
- AdminService
|
||||
- UsersController
|
||||
- UsersService
|
||||
- Repository Map
|
||||
- validate_integrity.js
|
||||
- admin-panel/package.json
|
||||
@ -159,15 +159,15 @@
|
||||
- application/package.json
|
||||
- start-dev.js
|
||||
- generate-openapi.js
|
||||
- PodcastPlayerModal.tsx
|
||||
- FaqService
|
||||
- SafeImage.tsx
|
||||
- AuthService
|
||||
- System Discovery
|
||||
- HomeController
|
||||
- class-transformer
|
||||
- SmsSettingsPage.tsx
|
||||
- track/page.tsx
|
||||
- trust-seals/page.tsx
|
||||
- Product Requirement Document (PRD)
|
||||
- helmet
|
||||
- RedisService
|
||||
- ValidateCouponDto
|
||||
- MetricsController
|
||||
- exclude
|
||||
- Baseline Command Plan & Reconciled Command History
|
||||
- seo-backfill.ts
|
||||
@ -191,15 +191,14 @@
|
||||
- rebuild_honest_ledger.js
|
||||
- validate_evidence_grade.js
|
||||
- uploads/[...path]/route.ts
|
||||
- videos/page.tsx
|
||||
- app/page.tsx
|
||||
- نقشه جامع پوشش تستهای سرتاسری (E2E Test Coverage Map) — پروژه Canina
|
||||
- app.e2e-spec.js
|
||||
- @types/node
|
||||
- API Contract Specification
|
||||
- ⚙️ Backend Technical Review (05_dev_backend)
|
||||
- 🎨 Frontend & Admin Panel Technical Review (06_dev_frontend)
|
||||
- 🚀 SEO & Content Strategy Review (12_seo_content)
|
||||
- js-yaml
|
||||
- @nestjs/core
|
||||
- eslint-config-next
|
||||
- seed-ui-texts.ts
|
||||
- seed-wiki.ts
|
||||
- update-blog.dto.ts
|
||||
@ -212,13 +211,10 @@
|
||||
- React + TypeScript + Vite
|
||||
- Select.tsx
|
||||
- userStore.ts
|
||||
- @nestjs/jwt
|
||||
- tailwindcss
|
||||
- application/README.md
|
||||
- deploy.sh
|
||||
- 🔒 Security & Performance Review (09_devops_security)
|
||||
- 👁️ UX & Persona Interface Review (08_visual_qa)
|
||||
- eslint
|
||||
- prisma/scientificTerms.ts
|
||||
- seed-blogs.ts
|
||||
- seed-custom.ts
|
||||
@ -237,21 +233,13 @@
|
||||
- Input.tsx
|
||||
- Textarea.tsx
|
||||
- admin-panel/tsconfig.json
|
||||
- catalog/page.tsx
|
||||
- eslint-config-prettier
|
||||
- next.config.ts
|
||||
- Shabnam Font README
|
||||
- AGENTS.md
|
||||
- rules/graphify.md
|
||||
- .agents/workflows/graphify.md
|
||||
- instructions.md
|
||||
- @nestjs/swagger
|
||||
- @nestjs/throttler
|
||||
- tailwindcss
|
||||
- passport
|
||||
- reflect-metadata
|
||||
- swagger-ui-express
|
||||
- @eslint/eslintrc
|
||||
- @vitejs/plugin-react
|
||||
- eslint-plugin-prettier
|
||||
- supertest
|
||||
@ -277,7 +265,6 @@
|
||||
- globals
|
||||
- jest
|
||||
- @nestjs/cli
|
||||
- @nestjs/testing
|
||||
- prettier
|
||||
- eslint-plugin-react-hooks
|
||||
- Canina Pharma GmbH
|
||||
@ -294,34 +281,20 @@
|
||||
- Production Docker Compose
|
||||
- Staging Docker Compose
|
||||
- ZibalService
|
||||
- @nestjs/schematics
|
||||
- ts-jest
|
||||
- ZibalEBankService
|
||||
- .initiateOrderPayment
|
||||
- @tailwindcss/postcss
|
||||
- typescript
|
||||
- @types/js-yaml
|
||||
- typescript-eslint
|
||||
- prisma
|
||||
- source-map-support
|
||||
- @types/supertest
|
||||
- @eslint/js
|
||||
- ts-loader
|
||||
- typescript-eslint
|
||||
- tsconfig-paths
|
||||
- @types/passport-jwt
|
||||
- 20260526160916_add_ui_texts_and_scientific_terms/migration.sql
|
||||
- @types/bcrypt
|
||||
- typescript
|
||||
- @types/compression
|
||||
- revalidate/route.ts
|
||||
- MaskableField.tsx
|
||||
- @tailwindcss/postcss
|
||||
- @types/express
|
||||
- @types/jest
|
||||
- @types/react-dom
|
||||
- eslint-plugin-react-refresh
|
||||
- @types/multer
|
||||
|
||||
## God Nodes (most connected - your core abstractions)
|
||||
1. `Roles()` - 106 edges
|
||||
@ -330,10 +303,10 @@
|
||||
4. `api` - 44 edges
|
||||
5. `SmsService` - 43 edges
|
||||
6. `PaginationDto` - 41 edges
|
||||
7. `Button()` - 39 edges
|
||||
8. `PaymentController` - 38 edges
|
||||
9. `ZibalService` - 37 edges
|
||||
10. `AdminService` - 36 edges
|
||||
7. `AdminService` - 40 edges
|
||||
8. `AdminController` - 39 edges
|
||||
9. `Button()` - 39 edges
|
||||
10. `PaymentController` - 38 edges
|
||||
|
||||
## Surprising Connections (you probably didn't know these)
|
||||
- `User Roles and Capabilities` --conceptually_related_to--> `User Profile Photo` [INFERRED]
|
||||
@ -352,7 +325,7 @@
|
||||
- 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`
|
||||
- 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`
|
||||
|
||||
## Communities (327 total, 116 thin omitted)
|
||||
## Communities (300 total, 93 thin omitted)
|
||||
|
||||
### Community 0 - "Roles"
|
||||
Cohesion: 0.24
|
||||
@ -360,7 +333,7 @@ Nodes (13): Roles(), PaymentController, ApiBearerAuth, ApiOperation, ApiTags, Bo
|
||||
|
||||
### Community 1 - "app.module.ts"
|
||||
Cohesion: 0.06
|
||||
Nodes (39): B2BModule, Module, BannersModule, Module, CmsModule, Module, RevalidationModule, Global (+31 more)
|
||||
Nodes (40): B2BModule, Module, BannersModule, Module, CmsModule, Module, RevalidationModule, Global (+32 more)
|
||||
|
||||
### Community 2 - "PaymentService"
|
||||
Cohesion: 0.11
|
||||
@ -368,11 +341,11 @@ Nodes (9): AdminTransactionFilterDto, ApiPropertyOptional, IsIn, IsNumber, IsOpt
|
||||
|
||||
### Community 3 - "productService.ts"
|
||||
Cohesion: 0.06
|
||||
Nodes (41): buildTorobProductSpec(), buildTorobProductTitle(), dynamic, GET(), revalidate, dynamic, GET(), revalidate (+33 more)
|
||||
Nodes (39): buildTorobProductSpec(), buildTorobProductTitle(), dynamic, GET(), revalidate, dynamic, GET(), revalidate (+31 more)
|
||||
|
||||
### Community 4 - "UserDashboard.tsx"
|
||||
Cohesion: 0.11
|
||||
Nodes (20): metadata, OrderDetailsModal(), PetProfile(), SearchResultsPage(), CURRENT_SYMPTOMS, MEDICAL_HISTORIES, SmartAdvisor(), SmartAdvisorProps (+12 more)
|
||||
### Community 4 - "PetProfile.tsx"
|
||||
Cohesion: 0.12
|
||||
Nodes (18): metadata, FeaturedProducts(), ProductCard(), OrderSuccess(), PetProfile(), SearchResultsPage(), CURRENT_SYMPTOMS, MEDICAL_HISTORIES (+10 more)
|
||||
|
||||
### Community 5 - "CmsController"
|
||||
Cohesion: 0.09
|
||||
@ -383,56 +356,56 @@ Cohesion: 0.09
|
||||
Nodes (31): AdminUpdateTicketDto, CreateTicketDto, ReplyTicketDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString (+23 more)
|
||||
|
||||
### Community 7 - "Button.tsx"
|
||||
Cohesion: 0.12
|
||||
Nodes (14): Button(), ButtonProps, ButtonSize, ButtonVariant, Spinner(), FAQ, BlogCommentItem, ProductReview (+6 more)
|
||||
Cohesion: 0.08
|
||||
Nodes (21): ButtonProps, ButtonSize, ButtonVariant, Spinner(), Doctor, FAQ, PatternItem, SmsConfigState (+13 more)
|
||||
|
||||
### Community 8 - "WikiController"
|
||||
Cohesion: 0.13
|
||||
Nodes (13): ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiTags, Controller, Get, Param, Query (+5 more)
|
||||
Cohesion: 0.21
|
||||
Nodes (9): ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiTags, Controller, Get, Param, Query (+1 more)
|
||||
|
||||
### Community 9 - "devDependencies"
|
||||
Cohesion: 0.22
|
||||
Nodes (9): devDependencies, ts-node, @types/bcryptjs, @types/node, typescript, @types/node, typescript, ts-node (+1 more)
|
||||
Cohesion: 0.05
|
||||
Nodes (43): devDependencies, eslint, eslint-config-prettier, @eslint/eslintrc, @nestjs/schematics, @nestjs/testing, prisma, source-map-support (+35 more)
|
||||
|
||||
### Community 10 - "CreateReviewDto"
|
||||
Cohesion: 0.07
|
||||
Nodes (30): CreateReviewDto, ApiProperty, IsInt, IsNotEmpty, IsOptional, IsString, Min, ApiProperty (+22 more)
|
||||
|
||||
### Community 11 - "MediaSelector.tsx"
|
||||
### Community 11 - "ConfirmModal.tsx"
|
||||
Cohesion: 0.06
|
||||
Nodes (35): ConfirmModal(), ConfirmModalProps, ImagePreviewModal(), ImagePreviewModalProps, getFileType(), Media, MediaSelector(), MediaSelectorProps (+27 more)
|
||||
Nodes (30): ConfirmModal(), ConfirmModalProps, Pagination(), PaginationProps, Category, HeroBanner, VetTestimonial, getFileType() (+22 more)
|
||||
|
||||
### Community 12 - "index.ts"
|
||||
Cohesion: 0.06
|
||||
Nodes (24): backend, frontend, playwright.config.ts, @playwright/test, tests/**/*.ts, authFile, test, compilerOptions (+16 more)
|
||||
|
||||
### Community 13 - "app-audit-verification.e2e-spec.js"
|
||||
Cohesion: 0.06
|
||||
Nodes (28): AppModule, Module, EnvironmentVariables, IsNotEmpty, IsOptional, IsString, validateEnv(), CustomHttpExceptionFilter (+20 more)
|
||||
Cohesion: 0.07
|
||||
Nodes (26): EnvironmentVariables, IsNotEmpty, IsOptional, IsString, validateEnv(), CustomHttpExceptionFilter, Catch, PrismaExceptionFilter (+18 more)
|
||||
|
||||
### Community 14 - "toPersian"
|
||||
Cohesion: 0.17
|
||||
Nodes (19): VerifyContent(), AddressFormData, AddressModal(), AddressModalProps, normalizeDigits(), validateAddressForm(), BackButton(), BackButtonProps (+11 more)
|
||||
Nodes (20): HomeClient(), VerifyContent(), AddressFormData, AddressModal(), AddressModalProps, normalizeDigits(), validateAddressForm(), CheckoutPage() (+12 more)
|
||||
|
||||
### Community 15 - "src/services/api.ts"
|
||||
Cohesion: 0.08
|
||||
Nodes (31): Layout(), ProtectedRoute(), MenuGroup, Sidebar(), SidebarProps, SubMenuItem, LiveMonitoringSummary, SEARCHABLE_PAGES (+23 more)
|
||||
|
||||
### Community 16 - "DoctorQueryDto"
|
||||
Cohesion: 0.09
|
||||
Nodes (24): DoctorsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+16 more)
|
||||
Cohesion: 0.08
|
||||
Nodes (26): DoctorsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+18 more)
|
||||
|
||||
### Community 17 - "schema.ts"
|
||||
Cohesion: 0.15
|
||||
Nodes (18): B2BPage(), generateMetadata(), ContactPage(), generateMetadata(), generateMetadata(), getRelatedProducts(), getWikiTerm(), WikiTermPage() (+10 more)
|
||||
Cohesion: 0.14
|
||||
Nodes (18): B2BPage(), generateMetadata(), ContactPage(), generateMetadata(), generateMetadata(), getInitialVideos(), Videos(), ContactFormClient() (+10 more)
|
||||
|
||||
### Community 18 - "JwtAuthGuard"
|
||||
Cohesion: 0.18
|
||||
Nodes (7): JwtAuthGuard, Injectable, ROLES_KEY, RequestWithUser, RolesGuard, Injectable, UserReqPayload
|
||||
Nodes (6): JwtAuthGuard, Injectable, ROLES_KEY, RequestWithUser, RolesGuard, Injectable
|
||||
|
||||
### Community 19 - "users.controller.ts"
|
||||
Cohesion: 0.13
|
||||
Nodes (13): AddressDto, ApiProperty, ApiPropertyOptional, IsBoolean, IsNotEmpty, IsOptional, IsString, ApiPropertyOptional (+5 more)
|
||||
### Community 19 - "admin.service.ts"
|
||||
Cohesion: 0.15
|
||||
Nodes (21): CouponInput, CouponTargetInput, PaginationQuery, ApplyGlobalMarginsDto, BulkPriceAdjustmentDto, ApiProperty, ApiPropertyOptional, IsArray (+13 more)
|
||||
|
||||
### Community 20 - "CreateVideoDto"
|
||||
Cohesion: 0.09
|
||||
@ -484,39 +457,39 @@ Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternati
|
||||
|
||||
### Community 32 - "adminRoutes.tsx"
|
||||
Cohesion: 0.08
|
||||
Nodes (17): App(), Props, RouteErrorBoundary, State, HeroBanner, VetTestimonial, AdminRouteConfig, CMS (+9 more)
|
||||
Nodes (14): App(), Props, RouteErrorBoundary, State, BestSellerItem, CategoryDistItem, COLORS, CustomTooltipProps (+6 more)
|
||||
|
||||
### Community 33 - "ContactService"
|
||||
Cohesion: 0.06
|
||||
Nodes (32): ContactController, Body, Controller, Get, Param, Post, Put, Query (+24 more)
|
||||
### Community 33 - "WholesaleApplyDto"
|
||||
Cohesion: 0.10
|
||||
Nodes (20): ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, WholesaleApplyDto, ApiBearerAuth, ApiOperation (+12 more)
|
||||
|
||||
### Community 34 - "B2BService"
|
||||
Cohesion: 0.13
|
||||
Nodes (15): B2BController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param (+7 more)
|
||||
|
||||
### Community 35 - "AuthController"
|
||||
Cohesion: 0.25
|
||||
Nodes (13): AuthController, ApiBadRequestResponse, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+5 more)
|
||||
### Community 35 - "ContactService"
|
||||
Cohesion: 0.13
|
||||
Nodes (12): ContactController, Body, Controller, Get, Param, Post, Put, Query (+4 more)
|
||||
|
||||
### Community 36 - "FaqController"
|
||||
### Community 36 - "FaqService"
|
||||
Cohesion: 0.14
|
||||
Nodes (12): FaqController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+4 more)
|
||||
Nodes (14): FaqController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
|
||||
|
||||
### Community 37 - "راهنمای تست سیستم (Software Testing)"
|
||||
Cohesion: 0.07
|
||||
Nodes (25): اجرای بکاند, اجرای فرانتاند, اجرای پروژه در سرور با PM2, برای راهاندازی بکاند:, برای راهاندازی فرانتاند Next.js:, بیلد کردن پروژه (Building), ذخیره پروسهها تا پس از ریاستارت سرور قطع نشوند:, راهاندازی و انتشار سیستم (Setup & Deployment) (+17 more)
|
||||
|
||||
### Community 38 - "Transactions.tsx"
|
||||
Cohesion: 0.10
|
||||
Nodes (19): TransactionReceiptData, TransactionReceiptModal(), TransactionReceiptModalProps, Badge(), BadgeProps, BadgeVariant, variantStyles, ThSort() (+11 more)
|
||||
### Community 38 - "Button"
|
||||
Cohesion: 0.16
|
||||
Nodes (13): TransactionReceiptData, TransactionReceiptModal(), TransactionReceiptModalProps, Button(), ThSort(), ThSortProps, GatewayHealth, Stats (+5 more)
|
||||
|
||||
### Community 39 - "CategoriesController"
|
||||
Cohesion: 0.10
|
||||
Nodes (16): CategoriesController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+8 more)
|
||||
|
||||
### Community 40 - "admin.module.ts"
|
||||
Cohesion: 0.09
|
||||
Nodes (19): AdminModule, Module, MediaController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller (+11 more)
|
||||
### Community 40 - "MediaController"
|
||||
Cohesion: 0.11
|
||||
Nodes (14): MediaController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
|
||||
|
||||
### Community 41 - "What You Must Do When Invoked"
|
||||
Cohesion: 0.07
|
||||
@ -547,8 +520,8 @@ Cohesion: 0.13
|
||||
Nodes (14): IngredientsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
|
||||
|
||||
### Community 48 - "auth.service.ts"
|
||||
Cohesion: 0.08
|
||||
Nodes (25): AdminLoginInput, LoginInput, RegisterInput, AdminLoginDto, ApiProperty, IsEmail, IsNotEmpty, IsString (+17 more)
|
||||
Cohesion: 0.06
|
||||
Nodes (46): AuthController, ApiBadRequestResponse, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+38 more)
|
||||
|
||||
### Community 49 - "devDependencies"
|
||||
Cohesion: 0.11
|
||||
@ -556,75 +529,75 @@ Nodes (19): autoprefixer, devDependencies, autoprefixer, eslint, @eslint/js, glo
|
||||
|
||||
### Community 50 - "devDependencies"
|
||||
Cohesion: 0.12
|
||||
Nodes (17): eslint-config-next, devDependencies, eslint, eslint-config-next, jsdom, @testing-library/jest-dom, @testing-library/react, @types/node (+9 more)
|
||||
Nodes (17): devDependencies, eslint, jsdom, tailwindcss, @testing-library/jest-dom, @testing-library/react, @types/node, @types/react (+9 more)
|
||||
|
||||
### Community 51 - "BlogsController"
|
||||
Cohesion: 0.14
|
||||
Nodes (16): BlogsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+8 more)
|
||||
Cohesion: 0.07
|
||||
Nodes (18): BlogsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+10 more)
|
||||
|
||||
### Community 52 - "PrescriptionsService"
|
||||
Cohesion: 0.14
|
||||
Nodes (14): PrescriptionsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param (+6 more)
|
||||
### Community 52 - "prescriptions.controller.ts"
|
||||
Cohesion: 0.11
|
||||
Nodes (17): PrescriptionsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param (+9 more)
|
||||
|
||||
### Community 53 - "SmartAdvisorService"
|
||||
Cohesion: 0.13
|
||||
Nodes (14): SmartAdvisorController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
|
||||
|
||||
### Community 54 - "Modal.tsx"
|
||||
Cohesion: 0.10
|
||||
Nodes (14): maxWidthClasses, Modal(), ModalProps, ContactInfoItem, ContactSubmission, MENU_TABS, MenuItem, MenuType (+6 more)
|
||||
Cohesion: 0.11
|
||||
Nodes (15): maxWidthClasses, Modal(), ModalProps, ContactInfoItem, ContactSubmission, BlogCommentItem, ProductReview, Reviews() (+7 more)
|
||||
|
||||
### Community 55 - "UITexts.tsx"
|
||||
Cohesion: 0.12
|
||||
Nodes (15): ICON_REGISTRY, IconPickerModal(), IconPickerModalProps, ToggleSwitch(), ToggleSwitchProps, AppSitePage, PageSection, SectionField (+7 more)
|
||||
|
||||
### Community 56 - "Orders.tsx"
|
||||
Cohesion: 0.11
|
||||
Nodes (15): Skeleton(), MonitoringStats, getPaymentMethodLabel(), Order, ORDER_STATUS_MAP, OrderItem, Orders(), PaymentTx (+7 more)
|
||||
### Community 56 - "PrescriptionsManager.tsx"
|
||||
Cohesion: 0.12
|
||||
Nodes (14): Badge(), BadgeProps, BadgeVariant, variantStyles, Skeleton(), MonitoringStats, ProductItem, UserRecord (+6 more)
|
||||
|
||||
### Community 57 - "Role & Core Objective"
|
||||
Cohesion: 0.09
|
||||
Nodes (22): CREATE MODE — Normal Operation, Expected JSON Output Schema, File Scope Boundary, Forbidden Actions, MODE 1: REVIEW (called during Review Phase), MODE 2: CONTENT CREATION (standalone task from backlog), Operating Modes, Operational Rules & Boundaries (+14 more)
|
||||
|
||||
### Community 58 - "auth.module.ts"
|
||||
Cohesion: 0.17
|
||||
Nodes (10): DEFAULT_JWT_REFRESH_SECRET, DEFAULT_JWT_SECRET, getJwtSecret(), AuthModule, Module, JwtPayload, JwtStrategy, Injectable (+2 more)
|
||||
### Community 58 - "UserDashboard.tsx"
|
||||
Cohesion: 0.12
|
||||
Nodes (11): DeleteConfirmModal(), DeleteConfirmModalProps, OrderDetailsModal(), OrderRowSkeleton(), PetProfileSkeleton(), Skeleton(), SkeletonProps, CreateTicketPayload (+3 more)
|
||||
|
||||
### Community 59 - "compilerOptions"
|
||||
Cohesion: 0.06
|
||||
Nodes (30): compilerOptions, allowSyntheticDefaultImports, baseUrl, declaration, emitDecoratorMetadata, esModuleInterop, experimentalDecorators, forceConsistentCasingInFileNames (+22 more)
|
||||
|
||||
### Community 60 - "admin.service.ts"
|
||||
Cohesion: 0.13
|
||||
Nodes (21): CouponInput, CouponTargetInput, PaginationQuery, ProductDto, ApiPropertyOptional, IsArray, IsBoolean, IsNumber (+13 more)
|
||||
### Community 60 - "CreateUserDto"
|
||||
Cohesion: 0.29
|
||||
Nodes (10): CreateUserDto, ApiProperty, ApiPropertyOptional, IsEmail, IsNotEmpty, IsNumber, IsOptional, IsString (+2 more)
|
||||
|
||||
### Community 61 - "ProductPage.tsx"
|
||||
Cohesion: 0.09
|
||||
Nodes (20): ProductImageZoomModalProps, CalculatorState, GalleryMediaItem, ICON_MAP, isVideoUrl(), ProductImageZoomModal, ProductPage(), ProductReviews (+12 more)
|
||||
Cohesion: 0.10
|
||||
Nodes (19): ProductImageZoomModalProps, CalculatorState, GalleryMediaItem, ICON_MAP, isVideoUrl(), ProductImageZoomModal, ProductPage(), ProductReviews (+11 more)
|
||||
|
||||
### Community 63 - "dependencies"
|
||||
Cohesion: 0.09
|
||||
Nodes (23): dependencies, bcrypt, bcryptjs, class-validator, compression, ioredis, @nestjs/common, @nestjs/passport (+15 more)
|
||||
Cohesion: 0.05
|
||||
Nodes (43): dependencies, bcrypt, bcryptjs, class-transformer, class-validator, compression, helmet, ioredis (+35 more)
|
||||
|
||||
### Community 64 - "compilerOptions"
|
||||
Cohesion: 0.10
|
||||
Nodes (20): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, lib, module, moduleDetection, moduleResolution, noEmit (+12 more)
|
||||
|
||||
### Community 65 - "BlogsService"
|
||||
Cohesion: 0.09
|
||||
Nodes (4): BlogsService, Injectable, RevalidationService, Injectable
|
||||
### Community 65 - "RevalidationService"
|
||||
Cohesion: 0.12
|
||||
Nodes (9): ProductDto, ApiPropertyOptional, IsArray, IsBoolean, IsNumber, IsOptional, IsString, RevalidationService (+1 more)
|
||||
|
||||
### Community 66 - "ApiOperation"
|
||||
Cohesion: 0.13
|
||||
Nodes (8): ApiOperation, ApiQuery, Get, Query, AdminQueryDto, ApiPropertyOptional, IsOptional, IsString
|
||||
### Community 66 - "Get"
|
||||
Cohesion: 0.17
|
||||
Nodes (7): ApiQuery, Get, Query, AdminQueryDto, ApiPropertyOptional, IsOptional, IsString
|
||||
|
||||
### Community 67 - "PetsController"
|
||||
Cohesion: 0.10
|
||||
Nodes (14): PetsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Delete, Get (+6 more)
|
||||
Cohesion: 0.11
|
||||
Nodes (13): PetsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Delete, Get (+5 more)
|
||||
|
||||
### Community 68 - "useSettingsStore"
|
||||
Cohesion: 0.08
|
||||
Nodes (36): HomeClient(), HomeClientProps, B2BLandingClient(), BlogPostClientProps, BlogPost, BlogPreviewSection(), ContactInfoItem, EnamadBadge() (+28 more)
|
||||
Nodes (29): HomeClientProps, B2BLandingClient(), BlogPost, BlogPreviewSection(), BrandLogo(), BrandLogoProps, ContactInfoItem, FAQItem (+21 more)
|
||||
|
||||
### Community 69 - "Required Review Group Closures"
|
||||
Cohesion: 0.10
|
||||
@ -635,8 +608,8 @@ Cohesion: 0.08
|
||||
Nodes (23): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, jsx, lib, module, moduleDetection, moduleResolution (+15 more)
|
||||
|
||||
### Community 71 - "getPageMetadata"
|
||||
Cohesion: 0.08
|
||||
Nodes (17): generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata(), getHomeData(), Home(), generateMetadata(), generateMetadata() (+9 more)
|
||||
Cohesion: 0.09
|
||||
Nodes (14): generateMetadata(), CatalogClient(), generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata() (+6 more)
|
||||
|
||||
### Community 72 - "Operational Rules & Boundaries"
|
||||
Cohesion: 0.11
|
||||
@ -655,8 +628,8 @@ Cohesion: 0.05
|
||||
Nodes (47): CreateHealthLogDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, CreatePetDto, ApiProperty (+39 more)
|
||||
|
||||
### Community 76 - "ProductsService"
|
||||
Cohesion: 0.07
|
||||
Nodes (27): GetProductsDto, ApiPropertyOptional, IsEnum, IsOptional, IsString, ProductsController, ApiOperation, ApiTags (+19 more)
|
||||
Cohesion: 0.09
|
||||
Nodes (19): GetProductsDto, ApiPropertyOptional, IsEnum, IsOptional, IsString, ProductsController, ApiOperation, ApiTags (+11 more)
|
||||
|
||||
### Community 77 - "seo.module.ts"
|
||||
Cohesion: 0.16
|
||||
@ -690,9 +663,9 @@ Nodes (21): dependencies, axios, lucide-react, react, react-dom, react-hot-toast
|
||||
Cohesion: 0.12
|
||||
Nodes (16): 1. Active Secret Scanning (All Modified Files), 2. Docker Container Verification (if Dockerfile exists), 3. CI/CD Basic Check (if `.github/workflows/` exists), 4. Failure Routing Protocol, 5. Forbidden Actions, ENFORCE MODE — Normal Operation, Expected JSON Output Schema, Operational Rules & Boundaries (+8 more)
|
||||
|
||||
### Community 85 - "Reports.tsx"
|
||||
Cohesion: 0.20
|
||||
Nodes (7): BestSellerItem, CategoryDistItem, COLORS, CustomTooltipProps, ReportData, TopCouponItem, Reports
|
||||
### Community 85 - "torob.controller.ts"
|
||||
Cohesion: 0.22
|
||||
Nodes (8): buildTorobProductSpec(), buildTorobProductTitle(), TorobController, ApiOperation, ApiTags, Controller, Get, Query
|
||||
|
||||
### Community 86 - "zibal.service.ts"
|
||||
Cohesion: 0.16
|
||||
@ -710,29 +683,25 @@ Nodes (8): CreateEBankCheckoutDto, ApiProperty, ApiPropertyOptional, IsNotEmpty,
|
||||
Cohesion: 0.17
|
||||
Nodes (12): prisma, main(), prisma, determineSuitableFor(), findOrCreateCategory(), getProductImageUrl(), main(), parseSize() (+4 more)
|
||||
|
||||
### Community 90 - "useCartStore"
|
||||
Cohesion: 0.09
|
||||
Nodes (19): B2BPortal, CartDrawer, B2BPortal(), CartDrawer(), DeleteConfirmModal(), DeleteConfirmModalProps, OrderSuccess(), OrderTracking() (+11 more)
|
||||
|
||||
### Community 91 - "Reconciled Audit Roles & Assignments"
|
||||
Cohesion: 0.12
|
||||
Nodes (15): 10. Documentation Engineer, 1. Lead Architect / Orchestrator, 2. React / Vite Storefront Auditor, 3. NestJS Backend Auditor, 4. Admin Features Auditor, 5. Database & Data Integrity Auditor, 6. Security Auditor, 7. TypeScript & Code Quality Auditor (+7 more)
|
||||
|
||||
### Community 92 - "OrdersService"
|
||||
Cohesion: 0.06
|
||||
Nodes (35): CreateOrderDto, OrderItemDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional (+27 more)
|
||||
Cohesion: 0.07
|
||||
Nodes (29): CreateOrderDto, OrderItemDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional (+21 more)
|
||||
|
||||
### Community 93 - "ArchivePage.tsx"
|
||||
Cohesion: 0.08
|
||||
Nodes (21): ArchivePage(), ArchiveProductCard(), CATEGORY_MAP, ICON_MAP, BannerPlacement(), BannerPlacementProps, OrderRowSkeleton(), PetProfileSkeleton() (+13 more)
|
||||
Cohesion: 0.13
|
||||
Nodes (15): ArchivePage(), ArchiveProductCard(), CATEGORY_MAP, ICON_MAP, BannerPlacement(), BannerPlacementProps, ProductCardSkeleton(), B2BInquiry (+7 more)
|
||||
|
||||
### Community 94 - "Phase 3.1 — Human Review Preparation and Master Backlog Critique"
|
||||
Cohesion: 0.13
|
||||
Nodes (14): 10. Dependency & Wave Adjustments, 11. Acceptance Criteria Refinements, 12. Business and Product Decision Classification, 13. Final Recommended Backlog Summary, 1. Executive Assessment, 2. Findings That Require No Change, 3. Findings Requiring Task Refinements, 4. Tasks Requiring Splitting (+6 more)
|
||||
|
||||
### Community 95 - "blog/[slug]/page.tsx"
|
||||
Cohesion: 0.26
|
||||
Nodes (11): BlogPostPage(), generateMetadata(), getBlog(), revalidate, safeIso(), safeLocalDate(), generateMetadata(), revalidate (+3 more)
|
||||
### Community 95 - "wiki/[slug]/page.tsx"
|
||||
Cohesion: 0.19
|
||||
Nodes (16): BlogPostPage(), generateMetadata(), getBlog(), revalidate, safeIso(), safeLocalDate(), generateMetadata(), revalidate (+8 more)
|
||||
|
||||
### Community 96 - "compilerOptions"
|
||||
Cohesion: 0.06
|
||||
@ -766,25 +735,25 @@ Nodes (13): jest, collectCoverageFrom, coverageDirectory, moduleFileExtensions,
|
||||
Cohesion: 0.15
|
||||
Nodes (12): 10. `TASK-VERIFY-001`, 1. `TASK-SEC-001`, 2. `TASK-SEC-002`, 3. `TASK-SEC-003`, 4. `TASK-FIN-001`, 5. `TASK-BUILD-001`, 6. `TASK-AUTH-001`, 7. `TASK-FE-001` (+4 more)
|
||||
|
||||
### Community 104 - "Coupons.tsx"
|
||||
Cohesion: 0.15
|
||||
Nodes (11): formatPriceWithCommas(), PriceInput(), PriceInputProps, toEnglishDigits(), Coupon, CouponFormData, CouponModalProps, CouponTarget (+3 more)
|
||||
### Community 104 - "Products.tsx"
|
||||
Cohesion: 0.09
|
||||
Nodes (23): formatPriceWithCommas(), PriceInput(), PriceInputProps, toEnglishDigits(), Coupon, CouponFormData, CouponModalProps, CouponTarget (+15 more)
|
||||
|
||||
### Community 105 - "Operational Rules & Boundaries"
|
||||
Cohesion: 0.17
|
||||
Nodes (11): 1. Detect Test Runner from Tech Stack, 2. Execution Output Capture (Mandatory), 3. Acceptance Criteria Verification, 4. Failure Routing Protocol, 5. Success Routing Protocol, 6. Forbidden Actions, Expected JSON Output Schema, Operational Rules & Boundaries (+3 more)
|
||||
|
||||
### Community 106 - "AuthService"
|
||||
Cohesion: 0.18
|
||||
Nodes (4): AuthService, Injectable, normalizeMobile(), UserAddressInput
|
||||
### Community 106 - "RedisService"
|
||||
Cohesion: 0.08
|
||||
Nodes (7): AppModule, Module, AuthService, Injectable, normalizeMobile(), RedisService, Injectable
|
||||
|
||||
### Community 107 - "PaginationDto"
|
||||
Cohesion: 0.07
|
||||
Nodes (20): CategoryQuery, BlogsModule, Module, BlogFilterDto, BlogsService, Injectable, PaginationDto, SortOrder (+12 more)
|
||||
Cohesion: 0.06
|
||||
Nodes (31): AdminModule, Module, MediaService, Injectable, SslCertInfo, BlogsModule, Module, BlogFilterDto (+23 more)
|
||||
|
||||
### Community 108 - "PrismaService"
|
||||
Cohesion: 0.07
|
||||
Nodes (20): WikiQuery, B2BWholesaleOrderItem, MeliPayamakPattern, MeliPayamakResponse, SendPatternSmsOptions, SmsConfig, SmsLogQuery, CreateContactSubmissionDto (+12 more)
|
||||
Cohesion: 0.06
|
||||
Nodes (22): CategoryQuery, PetQuery, WikiQuery, B2BWholesaleOrderItem, MeliPayamakPattern, MeliPayamakResponse, SendPatternSmsOptions, SmsConfig (+14 more)
|
||||
|
||||
### Community 109 - "1. Summary of Integrity Repairs Performed"
|
||||
Cohesion: 0.17
|
||||
@ -802,17 +771,17 @@ Nodes (10): 1. Mark Active Task as Complete, 2. Documentation Updates, 3. Dynami
|
||||
Cohesion: 0.18
|
||||
Nodes (10): 1. Pre-Deployment Checklist, 2. Build Verification, 3. Deployment Strategy (Based on Target), 4. Post-Deployment Health Check, 5. Forbidden Actions, Expected JSON Output Schema, Operational Rules & Boundaries, Required Output Artifacts (What files to write/update) (+2 more)
|
||||
|
||||
### Community 113 - "RegisterDto"
|
||||
### Community 113 - "Orders.tsx"
|
||||
Cohesion: 0.22
|
||||
Nodes (8): RegisterDto, ApiProperty, ApiPropertyOptional, IsEmail, IsNotEmpty, IsOptional, IsString, MinLength
|
||||
Nodes (8): getPaymentMethodLabel(), Order, ORDER_STATUS_MAP, OrderItem, Orders(), PaymentTx, toPersianDigits(), Orders
|
||||
|
||||
### Community 114 - "AppService"
|
||||
Cohesion: 0.29
|
||||
Nodes (5): AppController, Controller, Get, AppService, Injectable
|
||||
|
||||
### Community 115 - "Blogs.tsx"
|
||||
Cohesion: 0.12
|
||||
Nodes (14): ActiveStates, PRESET_BG_COLORS, PRESET_COLORS, RichTextEditor(), RichTextEditorProps, AuthorUser, BlogCategory, BlogPost (+6 more)
|
||||
### Community 115 - "MediaSelector.tsx"
|
||||
Cohesion: 0.07
|
||||
Nodes (23): ImagePreviewModal(), ImagePreviewModalProps, getFileType(), Media, MediaSelector(), MediaSelectorProps, ActiveStates, PRESET_BG_COLORS (+15 more)
|
||||
|
||||
### Community 116 - "Vazirmatn Changelog"
|
||||
Cohesion: 0.18
|
||||
@ -838,13 +807,9 @@ Nodes (9): compilerOptions, esModuleInterop, module, moduleResolution, skipLibCh
|
||||
Cohesion: 0.20
|
||||
Nodes (9): Compile and run the project, Deployment, Description, License, Project setup, Resources, Run tests, Stay in touch (+1 more)
|
||||
|
||||
### Community 122 - "AdminService"
|
||||
Cohesion: 0.15
|
||||
Nodes (4): Body, Post, AdminService, Injectable
|
||||
|
||||
### Community 123 - "UsersController"
|
||||
Cohesion: 0.20
|
||||
Nodes (16): ApiBadRequestResponse, ApiBearerAuth, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+8 more)
|
||||
### Community 123 - "UsersService"
|
||||
Cohesion: 0.06
|
||||
Nodes (42): DEFAULT_JWT_REFRESH_SECRET, DEFAULT_JWT_SECRET, getJwtSecret(), AuthModule, Module, JwtPayload, JwtStrategy, Injectable (+34 more)
|
||||
|
||||
### Community 124 - "Repository Map"
|
||||
Cohesion: 0.20
|
||||
@ -863,8 +828,8 @@ Cohesion: 0.20
|
||||
Nodes (9): Arch Linux, Contributors, Install, Known problems for variable version, License, Sahel-Font, To Do (variable), طریقه استفاده از نسخه متغیر variable (+1 more)
|
||||
|
||||
### Community 128 - "AdminController"
|
||||
Cohesion: 0.11
|
||||
Nodes (8): AdminController, ApiBearerAuth, ApiTags, Controller, Delete, Param, Put, UseGuards
|
||||
Cohesion: 0.18
|
||||
Nodes (11): AdminController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Param (+3 more)
|
||||
|
||||
### Community 130 - "Sahel-Font"
|
||||
Cohesion: 0.20
|
||||
@ -922,13 +887,9 @@ Nodes (7): backend, distMainPath, fs, http, path, { spawn, execSync }, waitForBa
|
||||
Cohesion: 0.25
|
||||
Nodes (6): app_module_1, core_1, fs, path, swagger_1, yaml
|
||||
|
||||
### Community 144 - "PodcastPlayerModal.tsx"
|
||||
Cohesion: 0.40
|
||||
Nodes (3): PLAYBACK_RATES, PodcastPlayerModal(), PodcastPlayerModalProps
|
||||
|
||||
### Community 145 - "FaqService"
|
||||
Cohesion: 0.33
|
||||
Nodes (4): FaqModule, Module, FaqService, Injectable
|
||||
### Community 144 - "SafeImage.tsx"
|
||||
Cohesion: 0.10
|
||||
Nodes (20): BackButton(), BackButtonProps, BlogPostClientProps, PLAYBACK_RATES, PodcastInlinePlayer(), PodcastInlinePlayerProps, PLAYBACK_RATES, PodcastPlayerModal() (+12 more)
|
||||
|
||||
### Community 146 - "System Discovery"
|
||||
Cohesion: 0.25
|
||||
@ -938,17 +899,17 @@ Nodes (7): Active Core Applications, Current Architecture, Evidence-Based Status
|
||||
Cohesion: 0.16
|
||||
Nodes (10): HomeController, ApiOkResponse, ApiOperation, ApiTags, Controller, Get, HomeModule, Module (+2 more)
|
||||
|
||||
### Community 149 - "SmsSettingsPage.tsx"
|
||||
Cohesion: 0.29
|
||||
Nodes (7): PatternItem, SmsConfigState, SmsLogItem, SmsLogStats, SmsSettingsPage(), toPersianDigits(), SmsSettingsPage
|
||||
|
||||
### Community 150 - "Product Requirement Document (PRD)"
|
||||
Cohesion: 0.29
|
||||
Nodes (6): 1. Executive Vision, 2. Target Audience, 3. Functional Requirements, 4. Non-Functional Requirements (Performance, Security), 5. Epic / Feature Breakdown, Product Requirement Document (PRD)
|
||||
|
||||
### Community 152 - "RedisService"
|
||||
Cohesion: 0.10
|
||||
Nodes (10): ApiExcludeController, MetricsController, Controller, Get, Res, RedisModule, Global, Module (+2 more)
|
||||
### Community 151 - "ValidateCouponDto"
|
||||
Cohesion: 0.50
|
||||
Nodes (4): IsNotEmpty, IsNumber, IsString, ValidateCouponDto
|
||||
|
||||
### Community 152 - "MetricsController"
|
||||
Cohesion: 0.29
|
||||
Nodes (5): ApiExcludeController, MetricsController, Controller, Get, Res
|
||||
|
||||
### Community 153 - "exclude"
|
||||
Cohesion: 0.22
|
||||
@ -1038,18 +999,14 @@ Nodes (4): activeFiles, errors, validationOutput, warnings
|
||||
Cohesion: 0.60
|
||||
Nodes (4): dynamic, GET(), getCandidateUrls(), HEAD()
|
||||
|
||||
### Community 177 - "videos/page.tsx"
|
||||
Cohesion: 0.47
|
||||
Nodes (5): generateMetadata(), getInitialVideos(), Videos(), VideosPage(), generateVideoObjectSchema()
|
||||
### Community 177 - "app/page.tsx"
|
||||
Cohesion: 0.67
|
||||
Nodes (3): generateMetadata(), getHomeData(), Home()
|
||||
|
||||
### Community 178 - "نقشه جامع پوشش تستهای سرتاسری (E2E Test Coverage Map) — پروژه Canina"
|
||||
Cohesion: 0.33
|
||||
Nodes (5): نقشه جامع پوشش تستهای سرتاسری (E2E Test Coverage Map) — پروژه Canina, ۱. معماری و نقشهای کاربری (User Roles), ۲. ماتریس جریانها و قابلیتهای کاربرمحور (Feature & Flow Matrix), ۳. وضعیت پوشش نهایی سوئیت ۱۱گانه (Final 11 Test Suites Status), ۴. راهنمای نگهداری و افزودن فیچرهای جدید بدون شکستن تستها (Developer Maintenance Guide)
|
||||
|
||||
### Community 179 - "app.e2e-spec.js"
|
||||
Cohesion: 0.50
|
||||
Nodes (3): app_module_1, supertest_1, testing_1
|
||||
|
||||
### Community 180 - "API Contract Specification"
|
||||
Cohesion: 0.50
|
||||
Nodes (3): 1. OpenAPI 3.0 (Swagger) Specification, 2. Endpoint Definitions & Data Types, API Contract Specification
|
||||
@ -1091,8 +1048,8 @@ Cohesion: 0.50
|
||||
Nodes (3): Select, SelectOption, SelectProps
|
||||
|
||||
### Community 197 - "userStore.ts"
|
||||
Cohesion: 0.06
|
||||
Nodes (33): AuthModal, ClientLayout(), LoginModal, AuthModal(), AuthModalProps, extractOtpFromText(), BrandLogo(), BrandLogoProps (+25 more)
|
||||
Cohesion: 0.08
|
||||
Nodes (37): AuthModal, B2BPortal, CartDrawer, ClientLayout(), LoginModal, AuthModal(), AuthModalProps, extractOtpFromText() (+29 more)
|
||||
|
||||
### Community 200 - "application/README.md"
|
||||
Cohesion: 0.50
|
||||
@ -1119,24 +1076,24 @@ Cohesion: 0.83
|
||||
Nodes (3): GET(), handleRevalidate(), POST()
|
||||
|
||||
## Knowledge Gaps
|
||||
- **1346 isolated node(s):** `$schema`, `collection`, `sourceRoot`, `deleteOutDir`, `name` (+1341 more)
|
||||
- **1348 isolated node(s):** `$schema`, `collection`, `sourceRoot`, `deleteOutDir`, `name` (+1343 more)
|
||||
These have ≤1 connection - possible missing edges or undocumented components.
|
||||
- **116 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
|
||||
- **93 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
|
||||
|
||||
## Suggested Questions
|
||||
_Questions this graph is uniquely positioned to answer:_
|
||||
|
||||
- **Why does `ApiResponse` connect `src/services/api.ts` to `BlogsController`, `AuthController`, `WikiController`, `PetsController`, `ProductsService`, `HomeController`, `UsersController`, `OrdersService`?**
|
||||
_High betweenness centrality (0.081) - this node is a cross-community bridge._
|
||||
- **Why does `Roles()` connect `Roles` to `ContactService`, `B2BService`, `FaqController`, `CmsController`, `tickets.controller.ts`, `SslController`, `BannersService`, `ProductsService`, `CreateReviewDto`, `TestimonialsService`, `IngredientsService`, `JwtAuthGuard`, `PrescriptionsService`, `SmartAdvisorService`, `SmsService`, `MenuService`?**
|
||||
_High betweenness centrality (0.065) - this node is a cross-community bridge._
|
||||
- **Why does `JwtAuthGuard` connect `JwtAuthGuard` to `WikiService`, `PetsController`, `CmsController`, `tickets.controller.ts`, `admin.module.ts`, `PaginationDto`, `PetsController`, `ProductsService`, `OrdersService`, `auth.service.ts`, `DoctorQueryDto`, `users.controller.ts`, `ReportsController`, `admin.service.ts`?**
|
||||
_High betweenness centrality (0.034) - this node is a cross-community bridge._
|
||||
- **Why does `Roles()` connect `Roles` to `WholesaleApplyDto`, `B2BService`, `ContactService`, `FaqService`, `CmsController`, `tickets.controller.ts`, `SslController`, `BannersService`, `ProductsService`, `CreateReviewDto`, `TestimonialsService`, `IngredientsService`, `JwtAuthGuard`, `prescriptions.controller.ts`, `SmartAdvisorService`, `SmsService`, `MenuService`?**
|
||||
_High betweenness centrality (0.068) - this node is a cross-community bridge._
|
||||
- **Why does `ApiResponse` connect `src/services/api.ts` to `BlogsController`, `WikiController`, `PetsController`, `ProductsService`, `auth.service.ts`, `HomeController`, `UsersService`, `OrdersService`?**
|
||||
_High betweenness centrality (0.062) - this node is a cross-community bridge._
|
||||
- **Why does `JwtAuthGuard` connect `JwtAuthGuard` to `CmsController`, `tickets.controller.ts`, `PaginationDto`, `PetsController`, `ProductsService`, `auth.service.ts`, `DoctorQueryDto`, `admin.service.ts`, `prescriptions.controller.ts`, `ReportsController`, `UsersService`?**
|
||||
_High betweenness centrality (0.028) - this node is a cross-community bridge._
|
||||
- **What connects `$schema`, `collection`, `sourceRoot` to the rest of the system?**
|
||||
_1346 weakly-connected nodes found - possible documentation gaps or missing edges._
|
||||
_1348 weakly-connected nodes found - possible documentation gaps or missing edges._
|
||||
- **Should `app.module.ts` be split into smaller, more focused modules?**
|
||||
_Cohesion score 0.062310949788263764 - nodes in this community are weakly interconnected._
|
||||
_Cohesion score 0.05786090005844535 - nodes in this community are weakly interconnected._
|
||||
- **Should `PaymentService` be split into smaller, more focused modules?**
|
||||
_Cohesion score 0.11 - nodes in this community are weakly interconnected._
|
||||
- **Should `productService.ts` be split into smaller, more focused modules?**
|
||||
_Cohesion score 0.055130784708249496 - nodes in this community are weakly interconnected._
|
||||
_Cohesion score 0.05780885780885781 - nodes in this community are weakly interconnected._
|
||||
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
16062
graphify-out/graph.json
16062
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