TorbatYar/frontend/modules/commercial/presentation.ts

862 lines
31 KiB
TypeScript

export type RegistryPresentationColumn = {
key: "name" | "status" | "version" | "updated_at" | "visibility";
label: string;
};
export type RegistryPresentation = {
label: string;
englishLabel: string;
pluralLabel: string;
description: string;
icon: string;
accentColor: string;
navigationTitle: string;
sectionTitle: string;
emptyStateTitle: string;
emptyStateDescription: string;
createButtonText: string;
editDialogTitle: string;
deleteConfirmation: string;
publishLabel: string;
archiveLabel: string;
statusMap: Readonly<Record<string, string>>;
columns: readonly RegistryPresentationColumn[];
};
const STATUS_LABELS: Readonly<Record<string, string>> = {
active: "فعال",
inactive: "غیرفعال",
draft: "پیش‌نویس",
published: "منتشرشده",
pending: "در انتظار بررسی",
pending_payment: "در انتظار پرداخت",
verified: "تأییدشده",
unverified: "تأییدنشده",
failed: "ناموفق",
succeeded: "موفق",
completed: "تکمیل‌شده",
expired: "منقضی‌شده",
trialing: "در دوره آزمایشی",
trial: "آزمایشی",
suspended: "تعلیق‌شده",
canceled: "لغوشده",
cancelled: "لغوشده",
archived: "بایگانی‌شده",
deprecated: "از رده خارج",
planned: "برنامه‌ریزی‌شده",
concept: "در حال طراحی",
mature: "پایدار",
ga: "آماده ارائه",
beta: "نسخه آزمایشی",
grace_period: "در مهلت تمدید",
processing: "در حال پردازش",
paid: "پرداخت‌شده",
unpaid: "پرداخت‌نشده",
refunded: "بازپرداخت‌شده",
partially_refunded: "بازپرداخت جزئی",
};
const DEFAULT_COLUMNS: readonly RegistryPresentationColumn[] = [
{ key: "name", label: "نام" },
{ key: "status", label: "وضعیت" },
{ key: "version", label: "نسخه" },
{ key: "updated_at", label: "آخرین تغییر" },
{ key: "visibility", label: "نمایش" },
];
function definePresentation({
label,
englishLabel,
pluralLabel = label,
description,
icon,
accentColor,
columns = DEFAULT_COLUMNS,
}: {
label: string;
englishLabel: string;
pluralLabel?: string;
description: string;
icon: string;
accentColor: string;
columns?: readonly RegistryPresentationColumn[];
}): RegistryPresentation {
return {
label,
englishLabel,
pluralLabel,
description,
icon,
accentColor,
navigationTitle: pluralLabel,
sectionTitle: `مدیریت ${pluralLabel}`,
emptyStateTitle: `هنوز ${pluralLabel} ثبت نشده است`,
emptyStateDescription: `پس از ثبت اولین مورد، اطلاعات ${pluralLabel} در این بخش نمایش داده می‌شود.`,
createButtonText: `افزودن ${label}`,
editDialogTitle: `ویرایش ${label}`,
deleteConfirmation: `آیا از حذف این ${label} مطمئن هستید؟`,
publishLabel: `انتشار ${label}`,
archiveLabel: `بایگانی ${label}`,
statusMap: STATUS_LABELS,
columns,
};
}
/**
* Universal Commercial Presentation Dictionary.
* API paths and registry records never provide user-facing copy.
*/
export const presentationRegistry = {
products: definePresentation({
label: "محصول",
englishLabel: "Product",
pluralLabel: "محصولات",
description: "راهکارهای نرم‌افزاری قابل ارائه به کسب‌وکارها",
icon: "◫",
accentColor: "bg-sky-50 text-sky-700",
}),
bundles: definePresentation({
label: "بسته خدمات",
englishLabel: "Service Bundle",
pluralLabel: "بسته‌های خدمات",
description: "ترکیب آماده محصولات و امکانات برای نیازهای مختلف",
icon: "▦",
accentColor: "bg-violet-50 text-violet-700",
}),
pricing: definePresentation({
label: "قیمت",
englishLabel: "Pricing Item",
pluralLabel: "قیمت‌گذاری",
description: "قیمت‌های ماهانه، سالانه، آزمایشی و سازمانی",
icon: "◉",
accentColor: "bg-emerald-50 text-emerald-700",
}),
plans: definePresentation({
label: "پلن اشتراک",
englishLabel: "Subscription Plan",
pluralLabel: "پلن‌های اشتراک",
description: "پلن‌های قابل انتخاب برای شروع و توسعه کسب‌وکار",
icon: "▤",
accentColor: "bg-indigo-50 text-indigo-700",
}),
capabilities: definePresentation({
label: "قابلیت",
englishLabel: "Capability",
pluralLabel: "قابلیت‌ها",
description: "امکانات کاربردی قابل ارائه در محصولات و بسته‌ها",
icon: "✦",
accentColor: "bg-amber-50 text-amber-700",
}),
"business-types": definePresentation({
label: "نوع کسب‌وکار",
englishLabel: "Business Type",
pluralLabel: "انواع کسب‌وکار",
description: "الگوهای کسب‌وکار برای پیشنهاد دقیق‌تر محصولات",
icon: "⌂",
accentColor: "bg-orange-50 text-orange-700",
}),
"recommendation-rules": definePresentation({
label: "قاعده پیشنهاد",
englishLabel: "Recommendation Rule",
pluralLabel: "قواعد پیشنهاد",
description: "قواعد تجاری برای پیشنهاد راهکار مناسب به هر کسب‌وکار",
icon: "◎",
accentColor: "bg-fuchsia-50 text-fuchsia-700",
}),
"assessment-schemas": definePresentation({
label: "الگوی ارزیابی",
englishLabel: "Assessment Schema",
pluralLabel: "الگوهای ارزیابی",
description: "پرسش‌های کسب‌وکاری برای شناخت نیاز مشتری",
icon: "☷",
accentColor: "bg-cyan-50 text-cyan-700",
}),
assets: definePresentation({
label: "دارایی دیجیتال",
englishLabel: "Digital Asset",
pluralLabel: "دارایی‌های دیجیتال",
description: "دارایی‌های قابل استفاده در محصولات و تجربه مشتری",
icon: "◇",
accentColor: "bg-rose-50 text-rose-700",
}),
extensions: definePresentation({
label: "افزونه",
englishLabel: "Extension",
pluralLabel: "افزونه‌ها",
description: "امکانات تکمیلی قابل افزودن به محصولات",
icon: "⊞",
accentColor: "bg-teal-50 text-teal-700",
}),
"automation-packs": definePresentation({
label: "بسته اتوماسیون",
englishLabel: "Automation Pack",
pluralLabel: "بسته‌های اتوماسیون",
description: "فرایندهای آماده برای کاهش کارهای تکراری",
icon: "⚙",
accentColor: "bg-blue-50 text-blue-700",
}),
metadata: definePresentation({
label: "ویژگی تکمیلی",
englishLabel: "Additional Attribute",
pluralLabel: "ویژگی‌های تکمیلی",
description: "اطلاعات تکمیلی سازمان‌یافته برای نمایش و دسته‌بندی",
icon: "≡",
accentColor: "bg-slate-100 text-slate-700",
}),
policies: definePresentation({
label: "قانون تجاری",
englishLabel: "Commercial Policy",
pluralLabel: "قوانین تجاری",
description: "قوانین دسترسی، دامنه، فروش و ارائه خدمات",
icon: "✓",
accentColor: "bg-lime-50 text-lime-700",
}),
"usage-plans": definePresentation({
label: "برنامه مصرف",
englishLabel: "Usage Plan",
pluralLabel: "برنامه‌های مصرف",
description: "سقف‌ها و الگوهای مصرف خدمات",
icon: "◔",
accentColor: "bg-purple-50 text-purple-700",
}),
subscriptions: definePresentation({
label: "اشتراک",
englishLabel: "Subscription",
pluralLabel: "اشتراک‌ها",
description: "اشتراک‌های فعال، آزمایشی و در انتظار تمدید مشتریان",
icon: "↻",
accentColor: "bg-sky-50 text-sky-700",
}),
licenses: definePresentation({
label: "لایسنس",
englishLabel: "License",
pluralLabel: "لایسنس‌ها",
description: "مجوزهای استفاده از محصولات و بسته‌های خریداری‌شده",
icon: "⌘",
accentColor: "bg-indigo-50 text-indigo-700",
}),
entitlements: definePresentation({
label: "مجوز دسترسی",
englishLabel: "Entitlement",
pluralLabel: "مجوزهای دسترسی",
description: "امکانات و سهمیه‌های در دسترس هر فضای کاری",
icon: "◈",
accentColor: "bg-emerald-50 text-emerald-700",
}),
promotions: definePresentation({
label: "پیشنهاد ویژه",
englishLabel: "Promotion",
pluralLabel: "پیشنهادهای ویژه",
description: "پیشنهادهای فروش زمان‌دار برای مشتریان",
icon: "★",
accentColor: "bg-pink-50 text-pink-700",
}),
coupons: definePresentation({
label: "کوپن",
englishLabel: "Coupon",
pluralLabel: "کوپن‌ها",
description: "کوپن‌های تخفیف قابل استفاده هنگام خرید",
icon: "✂",
accentColor: "bg-rose-50 text-rose-700",
}),
currencies: definePresentation({
label: "واحد پول",
englishLabel: "Currency",
pluralLabel: "واحدهای پول",
description: "واحدهای پول قابل استفاده در قیمت‌گذاری",
icon: "¤",
accentColor: "bg-green-50 text-green-700",
}),
taxes: definePresentation({
label: "گروه مالیاتی",
englishLabel: "Tax Class",
pluralLabel: "گروه‌های مالیاتی",
description: "قواعد محاسبه مالیات برای محصولات و خدمات",
icon: "%",
accentColor: "bg-amber-50 text-amber-700",
}),
notifications: definePresentation({
label: "اعلان تجاری",
englishLabel: "Commercial Notification",
pluralLabel: "اعلان‌های تجاری",
description: "پیام‌های مرتبط با خرید، اشتراک و تمدید",
icon: "◌",
accentColor: "bg-cyan-50 text-cyan-700",
}),
announcements: definePresentation({
label: "اطلاعیه تجاری",
englishLabel: "Commercial Announcement",
pluralLabel: "اطلاعیه‌های تجاری",
description: "اطلاعیه‌های قابل نمایش به مشتریان",
icon: "◍",
accentColor: "bg-blue-50 text-blue-700",
}),
transactions: definePresentation({
label: "تراکنش",
englishLabel: "Transaction",
pluralLabel: "تراکنش‌ها",
description: "سوابق پرداخت و بازپرداخت مشتریان",
icon: "⇄",
accentColor: "bg-teal-50 text-teal-700",
}),
invoices: definePresentation({
label: "صورتحساب",
englishLabel: "Invoice",
pluralLabel: "صورتحساب‌ها",
description: "صورتحساب‌های صادرشده برای اشتراک و خدمات",
icon: "▧",
accentColor: "bg-orange-50 text-orange-700",
}),
"trial-rules": definePresentation({
label: "قانون دوره آزمایشی",
englishLabel: "Trial Rule",
pluralLabel: "قوانین دوره آزمایشی",
description: "مدت، محدودیت و شرایط استفاده آزمایشی",
icon: "◷",
accentColor: "bg-green-50 text-green-700",
}),
"subscription-templates": definePresentation({
label: "الگوی اشتراک",
englishLabel: "Subscription Template",
pluralLabel: "الگوهای اشتراک",
description: "الگوهای آماده برای ایجاد و تمدید اشتراک",
icon: "▥",
accentColor: "bg-indigo-50 text-indigo-700",
}),
"discount-campaigns": definePresentation({
label: "کمپین تخفیف",
englishLabel: "Discount Campaign",
pluralLabel: "کمپین‌های تخفیف",
description: "کمپین‌های زمان‌دار تخفیف و فروش",
icon: "↓",
accentColor: "bg-red-50 text-red-700",
}),
"regional-pricing": definePresentation({
label: "قیمت منطقه‌ای",
englishLabel: "Regional Pricing",
pluralLabel: "قیمت‌های منطقه‌ای",
description: "تنظیم قیمت متناسب با منطقه و بازار",
icon: "⌖",
accentColor: "bg-cyan-50 text-cyan-700",
}),
languages: definePresentation({
label: "زبان",
englishLabel: "Language",
pluralLabel: "زبان‌ها",
description: "زبان‌های قابل استفاده در تجربه مشتری",
icon: "文",
accentColor: "bg-violet-50 text-violet-700",
}),
"marketplace-visibility": definePresentation({
label: "نحوه نمایش در بازار",
englishLabel: "Marketplace Visibility",
pluralLabel: "تنظیمات نمایش در بازار",
description: "کنترل نمایش محصولات و خدمات در بازار",
icon: "◐",
accentColor: "bg-slate-100 text-slate-700",
}),
"release-channels": definePresentation({
label: "کانال انتشار",
englishLabel: "Release Channel",
pluralLabel: "کانال‌های انتشار",
description: "مسیرهای عرضه نسخه‌های پایدار و آزمایشی",
icon: "➜",
accentColor: "bg-blue-50 text-blue-700",
}),
"support-levels": definePresentation({
label: "سطح پشتیبانی",
englishLabel: "Support Level",
pluralLabel: "سطوح پشتیبانی",
description: "سطوح پاسخ‌گویی و خدمات پشتیبانی",
icon: "☏",
accentColor: "bg-emerald-50 text-emerald-700",
}),
"activation-logs": definePresentation({
label: "فرایند فعال‌سازی",
englishLabel: "Activation Process",
pluralLabel: "فرایندهای فعال‌سازی",
description: "مراحل آماده‌سازی محصولات برای فضای کاری",
icon: "▶",
accentColor: "bg-lime-50 text-lime-700",
}),
} satisfies Record<string, RegistryPresentation>;
export type PresentationRegistryKind = keyof typeof presentationRegistry;
const KIND_ALIASES: Readonly<Record<string, PresentationRegistryKind>> = {
product: "products",
bundle: "bundles",
price: "pricing",
prices: "pricing",
plan: "plans",
capability: "capabilities",
"business-type": "business-types",
recommendations: "recommendation-rules",
recommendation: "recommendation-rules",
"recommendation-rules": "recommendation-rules",
assessments: "assessment-schemas",
"tax-classes": "taxes",
tax: "taxes",
notification: "notifications",
invoice: "invoices",
transaction: "transactions",
license: "licenses",
entitlement: "entitlements",
subscription: "subscriptions",
};
export function normalizeRegistryKind(kind: string): PresentationRegistryKind | null {
const normalized = kind.trim().toLowerCase().replace(/_/g, "-");
if (normalized in presentationRegistry) return normalized as PresentationRegistryKind;
return KIND_ALIASES[normalized] || null;
}
export function getRegistryPresentation(kind: string): RegistryPresentation {
const normalized = normalizeRegistryKind(kind);
if (normalized) return presentationRegistry[normalized];
return definePresentation({
label: "بخش تجاری جدید",
englishLabel: "New Commercial Section",
pluralLabel: "اطلاعات بخش تجاری جدید",
description: "اطلاعات این بخش پس از تکمیل عنوان نمایشی قابل مدیریت است.",
icon: "◈",
accentColor: "bg-slate-100 text-slate-700",
});
}
const TOKEN_LABELS: Readonly<Record<string, string>> = {
restaurant: "رستوران",
hospitality: "رستوران و پذیرایی",
food: "غذا",
beverage: "پذیرایی",
platform: "پلتفرم",
starter: "شروع",
start: "شروع",
growth: "رشد",
professional: "حرفه‌ای",
pro: "حرفه‌ای",
enterprise: "سازمانی",
loyalty: "باشگاه مشتریان",
delivery: "ارسال و پیک",
booking: "رزرو",
reservation: "رزرو",
payment: "پرداخت",
accounting: "حسابداری",
website: "وب‌سایت",
experience: "وب‌سایت و تجربه دیجیتال",
crm: "مدیریت ارتباط با مشتری",
inventory: "انبارداری",
pos: "صندوق فروش",
communication: "ارتباط با مشتریان",
marketplace: "بازار آنلاین",
sports: "مجموعه ورزشی",
"sports-center": "مجموعه ورزشی",
identity: "مدیریت کاربران",
operations: "عملیات",
sales: "فروش",
marketing: "بازاریابی",
finance: "مالی",
customers: "مشتریان",
online: "حضور آنلاین",
online_presence: "حضور آنلاین",
automation: "اتوماسیون",
ai: "هوش مصنوعی",
retail: "خرده‌فروشی",
service: "خدمات",
services: "خدمات",
ecommerce: "فروشگاه آنلاین",
clinic: "مرکز درمانی",
healthcare: "سلامت و درمان",
beauty: "زیبایی و سلامت",
education: "آموزش",
monthly: "ماهانه",
month: "ماهانه",
yearly: "سالانه",
annual: "سالانه",
year: "سالانه",
trial: "آزمایشی",
public: "قابل مشاهده برای همه",
private: "فقط مشتریان مجاز",
hidden: "پنهان",
admin: "فقط مدیران",
route: "داخل سامانه",
external: "پیوند بیرونی",
disabled: "غیرفعال",
deny: "غیرفعال",
allow: "فعال",
enterprise_quote: "استعلام قیمت",
enterprisequote: "استعلام قیمت",
irr: "ریال",
rial: "ریال",
recommended: "پیشنهاد‌شده",
popular: "محبوب",
most_popular: "محبوب‌ترین",
recurring: "اشتراکی",
flat: "مبلغ ثابت",
one_time: "پرداخت یک‌باره",
usage_based: "بر اساس مصرف",
per_user: "به‌ازای هر کاربر",
free: "رایگان",
standard: "استاندارد",
premium: "ویژه",
unlimited: "نامحدود",
required: "ضروری",
optional: "پیشنهادی",
later: "قابل افزودن در آینده",
...STATUS_LABELS,
};
export const FIELD_LABELS: Readonly<Record<string, string>> = {
display_name: "نام",
name: "نام",
title: "عنوان",
description: "توضیح کوتاه",
status: "وضعیت",
role: "نوع اشتراک",
pricing_model: "مدل پرداخت",
amount_minor: "قیمت",
currency: "واحد پول",
interval: "دوره پرداخت",
trial_days: "مدت دوره آزمایشی",
duration_days: "مدت دوره آزمایشی",
grace_period_days: "مهلت تمدید",
product_codes: "محصولات",
capability_codes: "قابلیت‌ها",
required_capabilities: "قابلیت‌های موردنیاز",
feature_refs: "امکانات",
pricing_refs: "روش‌های پرداخت",
discount_refs: "تخفیف‌ها",
coupon_code: "کد تخفیف",
tax_class_code: "گروه مالیاتی",
trial_eligible: "امکان استفاده آزمایشی",
visibility: "نحوه نمایش",
display_order: "اولویت نمایش",
category: "دسته‌بندی",
is_verified: "وضعیت تأیید",
domain_type: "نوع دامنه",
current_period_end: "تاریخ تمدید",
trial_ends_at: "پایان دوره آزمایشی",
created_at: "تاریخ ایجاد",
updated_at: "آخرین تغییر",
published_at: "تاریخ انتشار",
starts_at: "تاریخ شروع",
ends_at: "تاریخ پایان",
version: "نسخه",
metadata: "اطلاعات تکمیلی",
attributes: "ویژگی‌ها",
parameters: "تنظیمات",
usage_limits: "سقف استفاده",
feature_limits: "محدودیت امکانات",
auto_disable: "غیرفعال‌سازی خودکار",
expiration_behavior: "رفتار پس از پایان",
ssl_ready: "وضعیت گواهی امنیتی",
dns_instructions: "راهنمای اتصال دامنه",
custom_domain_allowed: "دامنه اختصاصی",
subdomain_only: "فقط زیردامنه",
reason: "دلیل پیشنهاد",
priority: "اولویت",
sellable: "قابل فروش",
lifecycle: "مرحله ارائه",
recommended_products: "محصولات ضروری",
recommended_bundles: "بسته‌های پیشنهادی",
recommended_services: "خدمات پیشنهادی",
recommended_upgrades: "پیشنهادهای قابل افزودن",
recommended_automation_packs: "اتوماسیون‌های پیشنهادی",
recommended_extensions: "افزونه‌های پیشنهادی",
score: "میزان تطبیق",
};
export const COMMERCIAL_FIELD_GROUPS = [
{
title: "اطلاعات پایه",
keys: ["display_name", "title", "description", "category"],
},
{
title: "اطلاعات تجاری",
keys: [
"role",
"pricing_model",
"amount_minor",
"currency",
"interval",
"trial_days",
"duration_days",
"grace_period_days",
],
},
{
title: "قیمت‌گذاری",
keys: ["pricing_refs", "tax_class_code", "discount_refs", "coupon_code"],
},
{
title: "قابلیت‌ها",
keys: [
"product_codes",
"capability_codes",
"feature_refs",
"required_capabilities",
],
},
{
title: "نمایش",
keys: ["visibility", "display_order", "trial_eligible"],
},
{
title: "انتشار",
keys: ["status", "lifecycle", "published_at", "starts_at", "ends_at"],
},
{
title: "اطلاعات تکمیلی",
keys: ["metadata", "attributes", "parameters", "usage_limits", "feature_limits"],
},
] as const;
export const FIELD_ENUM_OPTIONS: Readonly<Record<string, readonly string[]>> = {
status: [
"draft",
"active",
"published",
"inactive",
"suspended",
"archived",
],
visibility: ["public", "private", "hidden", "admin"],
pricing_model: [
"monthly",
"yearly",
"trial",
"enterprise_quote",
"one_time",
"usage_based",
],
interval: ["monthly", "yearly"],
currency: ["IRR"],
lifecycle: ["concept", "planned", "beta", "ga", "mature", "deprecated"],
};
const REFERENCE_KIND_BY_FIELD: Readonly<Record<string, PresentationRegistryKind>> = {
product_codes: "products",
products: "products",
recommended_products: "products",
recommended_services: "products",
bundle_codes: "bundles",
bundles: "bundles",
recommended_bundles: "bundles",
pricing_refs: "pricing",
pricing_items: "pricing",
capability_codes: "capabilities",
capabilities: "capabilities",
feature_refs: "capabilities",
required_capabilities: "capabilities",
recommended_upgrades: "products",
recommended_automation_packs: "automation-packs",
recommended_extensions: "extensions",
};
export function registryKindForField(
field?: string | null
): PresentationRegistryKind | null {
if (!field) return null;
return REFERENCE_KIND_BY_FIELD[field] || null;
}
const TECHNICAL_FIELD =
/(^id$|_id$|_code$|^code$|^kind$|^slug$|^stable_code$|^payload$|^raw_json$|^source$|^version_code$|^schema_version$|^rule_trace$|^href$|_route$|launch_strategy|created_by|updated_by|is_deleted|clone_of_id)/i;
export function isTechnicalField(key: string): boolean {
return TECHNICAL_FIELD.test(key);
}
export function fieldLabel(key: string): string {
if (FIELD_LABELS[key]) return FIELD_LABELS[key];
const translated = key
.split(/[_\-.]+/)
.map((token) => TOKEN_LABELS[token.toLowerCase()] || "")
.filter(Boolean)
.join(" ");
return translated || "ویژگی تکمیلی";
}
export function enumLabel(value: unknown): string {
if (typeof value === "boolean") return value ? "بله" : "خیر";
if (value == null || value === "") return "ثبت نشده";
const raw = String(value).trim();
const normalized = raw.toLowerCase().replace(/[-\s]+/g, "_");
const translated = TOKEN_LABELS[normalized] || humanizeCommercialCode(raw);
return translated === "عنوان تجاری ثبت‌نشده" ? "سایر" : translated;
}
function hasPersian(value?: string | null): boolean {
return Boolean(value && /[\u0600-\u06ff]/.test(value));
}
function translatedTokens(tokens: string[]): string[] {
return tokens
.map((token) => TOKEN_LABELS[token.toLowerCase()] || "")
.filter(Boolean);
}
type CodeFormatterContext = {
translated: string[];
subject: string;
interval: string;
};
const CODE_FORMATTERS: Readonly<
Record<string, (context: CodeFormatterContext) => string>
> = {
price: ({ translated, interval }) => {
const business = translated[0] || "کسب‌وکار";
const tier = translated.slice(1).join(" ");
return `پلن ${business}${tier ? ` — نسخه ${tier}` : ""}${interval ? ` (${interval})` : ""}`;
},
pricing: ({ translated, interval }) =>
CODE_FORMATTERS.price({ translated, subject: translated.join(" "), interval }),
plan: ({ translated, interval }) =>
CODE_FORMATTERS.price({ translated, subject: translated.join(" "), interval }),
bundle: ({ translated }) => {
const business = translated.find((token) => token !== "شروع") || "کسب‌وکار";
const tier = translated.includes("شروع") ? "شروع" : translated.slice(1).join(" ");
return `بسته ${tier || "خدمات"} ${business}`.trim();
},
product: ({ subject }) => `سیستم ${subject || "مدیریت کسب‌وکار"}`,
capability: ({ subject }) => subject || "قابلیت کاربردی",
feature: ({ subject }) => subject || "قابلیت کاربردی",
tax: ({ subject }) => subject || "گروه مالیاتی",
coupon: ({ subject }) => subject || "تخفیف",
discount: ({ subject }) => subject || "تخفیف",
};
export function humanizeCommercialCode(code: unknown, explicitName?: string | null): string {
if (explicitName && hasPersian(explicitName)) return explicitName.trim();
if (code == null) return "عنوان تجاری ثبت‌نشده";
const raw = String(code).trim();
if (!raw) return "عنوان تجاری ثبت‌نشده";
const normalized = raw.toLowerCase().replace(/_/g, "-");
if (TOKEN_LABELS[normalized]) return TOKEN_LABELS[normalized];
const tokens = raw.toLowerCase().split(/[._:/-]+/).filter(Boolean);
const kind = tokens[0];
const body = tokens.slice(1);
const intervalToken = body.find((token) => /^(monthly|month|yearly|annual|year|trial)$/.test(token));
const interval = intervalToken ? TOKEN_LABELS[intervalToken] : "";
const subjectTokens = body.filter((token) => token !== intervalToken);
const translated = translatedTokens(subjectTokens);
const subject = translated.join(" ");
const formatter = CODE_FORMATTERS[kind];
if (formatter) return formatter({ translated, subject, interval });
if (subject) return subject;
if (hasPersian(raw)) return raw;
return "عنوان تجاری ثبت‌نشده";
}
export function commercialEntityName(
kind: string,
code: unknown,
explicitName?: string | null
): string {
if (explicitName && hasPersian(explicitName)) return explicitName.trim();
const normalizedKind = normalizeRegistryKind(kind);
const raw = code == null ? "" : String(code);
const entityPrefixes: Partial<Record<PresentationRegistryKind, string>> = {
products: "product",
bundles: "bundle",
pricing: "price",
plans: "price",
capabilities: "capability",
};
const prefix = normalizedKind ? entityPrefixes[normalizedKind] : null;
if (prefix) {
const translated = humanizeCommercialCode(
raw.startsWith(`${prefix}.`) ? raw : `${prefix}.${raw}`
);
const genericFallbacks = [
"سیستم مدیریت کسب‌وکار",
"بسته خدمات کسب‌وکار",
"پلن کسب‌وکار",
"قابلیت کاربردی",
];
if (
genericFallbacks.includes(translated) &&
explicitName?.trim() &&
!looksTechnical(explicitName)
) {
return `${getRegistryPresentation(kind).label} ${explicitName.trim()}`;
}
return translated;
}
return humanizeCommercialCode(raw, explicitName);
}
export function commercialDescription(
kind: string,
description?: string | null
): string {
if (description && hasPersian(description)) return description.trim();
return getRegistryPresentation(kind).description;
}
export function looksTechnical(value: string): boolean {
return (
/[a-z0-9]+[._:/][a-z0-9_.:/-]+/i.test(value) ||
/^[a-z][a-z0-9_-]+$/i.test(value) ||
/\b(payload|json|registry|runtime|frontend|backend|metadata|heuristic|fallback|trace|api)\b/i.test(
value
)
);
}
export function formatMoney(amount?: number | null, currency?: string | null): string {
if (amount == null) return "استعلام قیمت";
const unit = !currency || /irr|rial/i.test(currency) ? "ریال" : enumLabel(currency);
return `${new Intl.NumberFormat("fa-IR").format(amount)} ${unit}`;
}
export function formatDate(value?: string | null): string {
if (!value) return "ثبت نشده";
const date = new Date(value);
return Number.isNaN(date.getTime())
? "ثبت نشده"
: new Intl.DateTimeFormat("fa-IR", { dateStyle: "medium" }).format(date);
}
export function friendlyMessage(
message?: string | null,
fallback = "در حال حاضر اطلاعاتی برای نمایش نداریم."
): string {
if (!message || looksTechnical(message) || /\b(4\d\d|5\d\d)\b/.test(message)) {
return fallback;
}
return message.replace(/\[object Object\]/g, "اطلاعات تکمیلی");
}
export function recommendationReason(reason?: string | null): string {
if (!reason || looksTechnical(reason)) return "بر اساس پاسخ‌های شما";
const normalized = reason.toLowerCase();
if (/branch|شعب/.test(normalized)) return "چون چند شعبه دارید";
if (/delivery|ارسال/.test(normalized)) return "چون ارسال کالا دارید";
if (/online|website|وب/.test(normalized)) return "چون فروش آنلاین دارید";
if (/pos|in.person|حضوری|فروش/.test(normalized)) return "چون فروش حضوری دارید";
if (/loyal|باشگاه/.test(normalized)) return "چون باشگاه مشتریان برای شما مهم است";
if (/booking|reservation|رزرو/.test(normalized)) return "چون به مدیریت رزرو نیاز دارید";
return reason;
}
export function presentValue(
value: unknown
): string | Array<string | Record<string, unknown>> | Record<string, unknown> {
if (Array.isArray(value)) {
return value.map((item) => {
if (item && typeof item === "object") return item as Record<string, unknown>;
return typeof item === "string" ? humanizeCommercialCode(item) : enumLabel(item);
});
}
if (value && typeof value === "object") return value as Record<string, unknown>;
if (typeof value === "string") return enumLabel(value);
if (typeof value === "boolean") return value ? "بله" : "خیر";
if (typeof value === "number") return new Intl.NumberFormat("fa-IR").format(value);
return "ثبت نشده";
}