feat(admin): implement smart pricing, rounding rules, formula margins and bulk price adjustment
Some checks failed
Deploy Canina / deploy (push) Successful in 45s
E2E Playwright Tests / Run Full E2E & Security Suites (push) Failing after 6s

This commit is contained in:
parsa aghaei 2026-09-02 10:05:41 +03:30
parent 382ebc0ce6
commit ac15ece619
21 changed files with 17219 additions and 14083 deletions

View File

@ -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)

View File

@ -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: 'شماره صفحه' })

View File

@ -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,
};
}
}

View 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';
}

View File

@ -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: 'نمایش متنی قیمت' })

View 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);
}

View File

@ -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>

View File

@ -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}

View File

@ -213,4 +213,11 @@ export interface Product {
stockQuantity?: number;
}
export interface PricingSettings {
roundingStep: number;
roundingMode: 'UP' | 'DOWN' | 'NEAREST';
defaultRetailMarginPercent: number;
defaultWholesaleMarginPercent: number;
}

View 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);
}

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -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._

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff