feat(commercial): register workspace product and pricing
EOF Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
d0c1d296b2
commit
0135c2137e
@ -228,7 +228,8 @@ class AssessmentEngine:
|
||||
"options": [
|
||||
{"value": "online_sales", "label": "فروش آنلاین"},
|
||||
{"value": "delivery", "label": "پیک"},
|
||||
{"value": "crm", "label": "CRM"},
|
||||
{"value": "crm", "label": "مدیریت ارتباط با مشتری"},
|
||||
{"value": "workspace", "label": "فضای کاری"},
|
||||
{"value": "loyalty", "label": "وفاداری"},
|
||||
{"value": "website", "label": "وبسایت"},
|
||||
],
|
||||
|
||||
@ -301,10 +301,10 @@ class LicenseResolver:
|
||||
state = (await self.db.execute(stmt)).scalar_one_or_none()
|
||||
if state is None:
|
||||
steps = [
|
||||
{"step_key": "workspace", "label": "Workspace", "status": "done"},
|
||||
{"step_key": "bundle", "label": "Bundle", "status": "done"},
|
||||
{"step_key": "trial", "label": "Trial", "status": "done"},
|
||||
{"step_key": "dashboard", "label": "Dashboard", "status": "pending", "href": "/dashboard"},
|
||||
{"step_key": "workspace", "label": "فضای کاری", "status": "done"},
|
||||
{"step_key": "bundle", "label": "بسته خدمات", "status": "done"},
|
||||
{"step_key": "trial", "label": "دوره آزمایشی", "status": "done"},
|
||||
{"step_key": "dashboard", "label": "داشبورد", "status": "pending", "href": "/dashboard"},
|
||||
]
|
||||
state = CommercialActivationState(
|
||||
tenant_id=tenant_id,
|
||||
@ -312,9 +312,9 @@ class LicenseResolver:
|
||||
completion_percent=75,
|
||||
steps=steps,
|
||||
checklist=[
|
||||
{"id": "workspace", "label": "Workspace", "done": True},
|
||||
{"id": "bundle", "label": "انتخاب باندل", "done": True},
|
||||
{"id": "billing", "label": "Billing", "done": False, "href": "/billing"},
|
||||
{"id": "workspace", "label": "فضای کاری", "done": True},
|
||||
{"id": "bundle", "label": "انتخاب بسته", "done": True},
|
||||
{"id": "billing", "label": "صورتحساب", "done": False, "href": "/billing"},
|
||||
{"id": "domain", "label": "دامنه", "done": False, "href": "/domains"},
|
||||
],
|
||||
)
|
||||
|
||||
@ -341,3 +341,11 @@ async def test_policy_evaluate(client):
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["custom_domain_allowed"] is True
|
||||
|
||||
|
||||
def test_workspace_commercial_registry_catalog():
|
||||
from pathlib import Path
|
||||
import runpy
|
||||
|
||||
script = Path(__file__).resolve().parents[2] / "scripts" / "validate_commercial_workspace_registry.py"
|
||||
runpy.run_path(str(script), run_name="__validate_workspace_registry__")
|
||||
|
||||
324
backend/core-service/scripts/data/business-bundles.catalog.yaml
Normal file
324
backend/core-service/scripts/data/business-bundles.catalog.yaml
Normal file
@ -0,0 +1,324 @@
|
||||
# Canonical machine-readable Business Bundle catalog.
|
||||
# Source of truth with docs/reference/business-bundles.md
|
||||
# Frontend MUST be generated from this file in a future phase — do not treat React as authoritative.
|
||||
schema_version: 1
|
||||
contract_version: business-bundle.v1
|
||||
updated: "2026-07-28"
|
||||
owner: core-platform
|
||||
|
||||
bundles:
|
||||
- bundle_code: bundle.restaurant.starter
|
||||
display_name: Restaurant Starter
|
||||
status: active
|
||||
version_code: "2026.07.1"
|
||||
business_types: [restaurant]
|
||||
services:
|
||||
- { service_identifier: hospitality, required: true }
|
||||
- { service_identifier: payment, required: true }
|
||||
- { service_identifier: experience, required: true }
|
||||
- { service_identifier: delivery, required: false }
|
||||
- { service_identifier: loyalty, required: false }
|
||||
- { service_identifier: communication, required: false }
|
||||
- { service_identifier: accounting, required: false }
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.restaurant.starter.monthly, role: primary }
|
||||
- { pricing_item_code: price.restaurant.starter.yearly, role: primary }
|
||||
visibility: { public_catalog: true, admin_only: false, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: true, grandfathered: false }
|
||||
|
||||
- bundle_code: bundle.restaurant.growth
|
||||
display_name: Restaurant Growth
|
||||
status: active
|
||||
version_code: "2026.07.1"
|
||||
business_types: [restaurant]
|
||||
services:
|
||||
- { service_identifier: hospitality, required: true }
|
||||
- { service_identifier: payment, required: true }
|
||||
- { service_identifier: experience, required: true }
|
||||
- { service_identifier: delivery, required: true }
|
||||
- { service_identifier: loyalty, required: true }
|
||||
- { service_identifier: communication, required: true }
|
||||
- { service_identifier: accounting, required: false }
|
||||
- { service_identifier: crm, required: false }
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.restaurant.growth.monthly, role: primary }
|
||||
visibility: { public_catalog: true, admin_only: false, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: true, grandfathered: false }
|
||||
|
||||
- bundle_code: bundle.cafe.starter
|
||||
display_name: Cafe Starter
|
||||
status: active
|
||||
version_code: "2026.07.1"
|
||||
business_types: [cafe]
|
||||
services:
|
||||
- { service_identifier: hospitality, required: true }
|
||||
- { service_identifier: payment, required: true }
|
||||
- { service_identifier: experience, required: true }
|
||||
- { service_identifier: loyalty, required: false }
|
||||
- { service_identifier: communication, required: false }
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.cafe.starter.monthly, role: primary }
|
||||
visibility: { public_catalog: true, admin_only: false, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: true, grandfathered: false }
|
||||
|
||||
- bundle_code: bundle.clinic.starter
|
||||
display_name: Clinic Starter
|
||||
status: active
|
||||
version_code: "2026.07.1"
|
||||
business_types: [clinic]
|
||||
services:
|
||||
- { service_identifier: experience, required: true }
|
||||
- { service_identifier: communication, required: true }
|
||||
- { service_identifier: accounting, required: true }
|
||||
- { service_identifier: payment, required: false }
|
||||
- { service_identifier: crm, required: false }
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.clinic.starter.monthly, role: primary }
|
||||
visibility: { public_catalog: true, admin_only: false, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: true, grandfathered: false }
|
||||
|
||||
- bundle_code: bundle.beauty.starter
|
||||
display_name: Beauty Salon Starter
|
||||
status: active
|
||||
version_code: "2026.07.1"
|
||||
business_types: [beauty_salon]
|
||||
services:
|
||||
- { service_identifier: experience, required: true }
|
||||
- { service_identifier: crm, required: true }
|
||||
- { service_identifier: payment, required: true }
|
||||
- { service_identifier: communication, required: true }
|
||||
- { service_identifier: loyalty, required: false }
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.beauty.starter.monthly, role: primary }
|
||||
visibility: { public_catalog: true, admin_only: false, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: true, grandfathered: false }
|
||||
|
||||
- bundle_code: bundle.sports.starter
|
||||
display_name: Sports Center Starter
|
||||
status: active
|
||||
version_code: "2026.07.1"
|
||||
business_types: [sports_center]
|
||||
services:
|
||||
- { service_identifier: sports-center, required: true }
|
||||
- { service_identifier: payment, required: true }
|
||||
- { service_identifier: loyalty, required: true }
|
||||
- { service_identifier: communication, required: false }
|
||||
- { service_identifier: accounting, required: false }
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.sports.starter.monthly, role: primary }
|
||||
visibility: { public_catalog: true, admin_only: false, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: true, grandfathered: false }
|
||||
|
||||
- bundle_code: bundle.retail.starter
|
||||
display_name: Retail Starter
|
||||
status: active
|
||||
version_code: "2026.07.1"
|
||||
business_types: [retail_shop]
|
||||
services:
|
||||
- { service_identifier: experience, required: true }
|
||||
- { service_identifier: payment, required: true }
|
||||
- { service_identifier: delivery, required: false }
|
||||
- { service_identifier: loyalty, required: false }
|
||||
- { service_identifier: accounting, required: false }
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.retail.starter.monthly, role: primary }
|
||||
visibility: { public_catalog: true, admin_only: false, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: true, grandfathered: false }
|
||||
|
||||
- bundle_code: bundle.marketplace.seller
|
||||
display_name: Marketplace Seller
|
||||
status: draft
|
||||
version_code: "2026.07.1"
|
||||
business_types: [marketplace_seller]
|
||||
services:
|
||||
- { service_identifier: payment, required: true }
|
||||
- { service_identifier: accounting, required: true }
|
||||
- { service_identifier: communication, required: true }
|
||||
- { service_identifier: marketplace, required: false }
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.marketplace.seller.monthly, role: primary }
|
||||
visibility: { public_catalog: false, admin_only: true, hidden: false }
|
||||
lifecycle: { sellable: false, renewable: false, grandfathered: false }
|
||||
notes: Marketplace product not implemented in commercial-foundation wave.
|
||||
|
||||
- bundle_code: bundle.education.starter
|
||||
display_name: Education Starter
|
||||
status: active
|
||||
version_code: "2026.07.1"
|
||||
business_types: [educational_center]
|
||||
services:
|
||||
- { service_identifier: experience, required: true }
|
||||
- { service_identifier: crm, required: true }
|
||||
- { service_identifier: communication, required: true }
|
||||
- { service_identifier: payment, required: false }
|
||||
- { service_identifier: accounting, required: false }
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.education.starter.monthly, role: primary }
|
||||
visibility: { public_catalog: true, admin_only: false, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: true, grandfathered: false }
|
||||
|
||||
- bundle_code: bundle.corporate.crm
|
||||
display_name: Corporate CRM
|
||||
status: active
|
||||
version_code: "2026.07.1"
|
||||
business_types: [corporate_company]
|
||||
services:
|
||||
- { service_identifier: crm, required: true }
|
||||
- { service_identifier: accounting, required: true }
|
||||
- { service_identifier: communication, required: true }
|
||||
- { service_identifier: identity-access, required: false }
|
||||
- { service_identifier: experience, required: false }
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.corporate.crm.monthly, role: primary }
|
||||
visibility: { public_catalog: true, admin_only: false, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: true, grandfathered: false }
|
||||
|
||||
- bundle_code: bundle.professional.starter
|
||||
display_name: Professional Services
|
||||
status: active
|
||||
version_code: "2026.07.1"
|
||||
business_types: [professional_services]
|
||||
services:
|
||||
- { service_identifier: crm, required: true }
|
||||
- { service_identifier: accounting, required: true }
|
||||
- { service_identifier: experience, required: true }
|
||||
- { service_identifier: communication, required: false }
|
||||
- { service_identifier: payment, required: false }
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.professional.starter.monthly, role: primary }
|
||||
visibility: { public_catalog: true, admin_only: false, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: true, grandfathered: false }
|
||||
|
||||
- bundle_code: bundle.freelancer.starter
|
||||
display_name: Freelancer
|
||||
status: active
|
||||
version_code: "2026.07.1"
|
||||
business_types: [freelancer]
|
||||
services:
|
||||
- { service_identifier: experience, required: true }
|
||||
- { service_identifier: payment, required: true }
|
||||
- { service_identifier: accounting, required: false }
|
||||
- { service_identifier: communication, required: false }
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.freelancer.starter.monthly, role: primary }
|
||||
visibility: { public_catalog: true, admin_only: false, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: true, grandfathered: false }
|
||||
|
||||
- bundle_code: bundle.manufacturer.starter
|
||||
display_name: Manufacturer
|
||||
status: active
|
||||
version_code: "2026.07.1"
|
||||
business_types: [manufacturer]
|
||||
services:
|
||||
- { service_identifier: crm, required: true }
|
||||
- { service_identifier: accounting, required: true }
|
||||
- { service_identifier: communication, required: false }
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.manufacturer.starter.monthly, role: primary }
|
||||
visibility: { public_catalog: true, admin_only: false, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: true, grandfathered: false }
|
||||
|
||||
- bundle_code: bundle.wholesale.starter
|
||||
display_name: Wholesale
|
||||
status: active
|
||||
version_code: "2026.07.1"
|
||||
business_types: [wholesale]
|
||||
services:
|
||||
- { service_identifier: crm, required: true }
|
||||
- { service_identifier: accounting, required: true }
|
||||
- { service_identifier: payment, required: true }
|
||||
- { service_identifier: delivery, required: false }
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.wholesale.starter.monthly, role: primary }
|
||||
visibility: { public_catalog: true, admin_only: false, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: true, grandfathered: false }
|
||||
|
||||
- bundle_code: bundle.real_estate.starter
|
||||
display_name: Real Estate
|
||||
status: active
|
||||
version_code: "2026.07.1"
|
||||
business_types: [real_estate]
|
||||
services:
|
||||
- { service_identifier: experience, required: true }
|
||||
- { service_identifier: crm, required: true }
|
||||
- { service_identifier: communication, required: true }
|
||||
- { service_identifier: payment, required: false }
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.real_estate.starter.monthly, role: primary }
|
||||
visibility: { public_catalog: true, admin_only: false, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: true, grandfathered: false }
|
||||
|
||||
- bundle_code: bundle.government.starter
|
||||
display_name: Government
|
||||
status: active
|
||||
version_code: "2026.07.1"
|
||||
business_types: [government]
|
||||
services:
|
||||
- { service_identifier: experience, required: true }
|
||||
- { service_identifier: communication, required: true }
|
||||
- { service_identifier: accounting, required: true }
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.enterprise.custom, role: enterprise_quote }
|
||||
visibility: { public_catalog: false, admin_only: true, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: true, grandfathered: false }
|
||||
|
||||
- bundle_code: bundle.ngo.starter
|
||||
display_name: NGO
|
||||
status: active
|
||||
version_code: "2026.07.1"
|
||||
business_types: [ngo]
|
||||
services:
|
||||
- { service_identifier: experience, required: true }
|
||||
- { service_identifier: crm, required: true }
|
||||
- { service_identifier: communication, required: true }
|
||||
- { service_identifier: accounting, required: false }
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.ngo.starter.monthly, role: primary }
|
||||
visibility: { public_catalog: true, admin_only: false, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: true, grandfathered: false }
|
||||
|
||||
- bundle_code: bundle.platform.trial
|
||||
display_name: Platform Trial
|
||||
status: active
|
||||
version_code: "2026.07.1"
|
||||
business_types: [custom]
|
||||
services:
|
||||
- { service_identifier: experience, required: true }
|
||||
- { service_identifier: payment, required: false }
|
||||
- { service_identifier: communication, required: false }
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.platform.trial.14d, role: trial }
|
||||
visibility: { public_catalog: true, admin_only: false, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: false, grandfathered: false }
|
||||
|
||||
- bundle_code: bundle.workspace.starter
|
||||
display_name: فضای کاری — نسخه شروع
|
||||
status: active
|
||||
version_code: "2026.07.1"
|
||||
business_types:
|
||||
- advertising_agency
|
||||
- content_team
|
||||
- software_company
|
||||
- marketing_company
|
||||
- team_freelancer
|
||||
services:
|
||||
- { service_identifier: workspace, required: true }
|
||||
- { service_identifier: communication, required: true }
|
||||
- { service_identifier: crm, required: true }
|
||||
- { service_identifier: accounting, required: true }
|
||||
capability_codes:
|
||||
- workspace.projects
|
||||
- workspace.tasks
|
||||
- workspace.kanban
|
||||
- workspace.calendar
|
||||
- workspace.content
|
||||
- workspace.approvals
|
||||
- workspace.comments
|
||||
- workspace.mentions
|
||||
- workspace.analytics
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.workspace.starter.monthly, role: primary }
|
||||
- { pricing_item_code: price.workspace.business.monthly, role: upgrade }
|
||||
- { pricing_item_code: price.workspace.enterprise, role: enterprise }
|
||||
visibility: { public_catalog: true, admin_only: false, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: true, grandfathered: false }
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate Workspace Commercial Runtime registration (catalog + seed, no extra services)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
CATALOG = ROOT / "scripts" / "data" / "business-bundles.catalog.yaml"
|
||||
SEED = ROOT / "scripts" / "seed_commercial_runtime.py"
|
||||
|
||||
REQUIRED_PRODUCTS = {"workspace", "communication", "crm", "accounting"}
|
||||
REQUIRED_PRICES = {
|
||||
"price.workspace.starter.monthly",
|
||||
"price.workspace.business.monthly",
|
||||
"price.workspace.enterprise",
|
||||
}
|
||||
REQUIRED_CAPS = {
|
||||
"workspace.projects",
|
||||
"workspace.tasks",
|
||||
"workspace.kanban",
|
||||
"workspace.calendar",
|
||||
"workspace.content",
|
||||
"workspace.approvals",
|
||||
"workspace.comments",
|
||||
"workspace.mentions",
|
||||
"workspace.analytics",
|
||||
}
|
||||
REQUIRED_TYPES = {
|
||||
"advertising_agency",
|
||||
"content_team",
|
||||
"software_company",
|
||||
"marketing_company",
|
||||
"team_freelancer",
|
||||
}
|
||||
REQUIRED_RULES = {
|
||||
"r.workspace.advertising_agency",
|
||||
"r.workspace.content_team",
|
||||
"r.workspace.software_company",
|
||||
"r.workspace.marketing_company",
|
||||
"r.workspace.team_freelancer",
|
||||
}
|
||||
|
||||
|
||||
def fail(msg: str) -> None:
|
||||
print(f"FAIL {msg}")
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
catalog = yaml.safe_load(CATALOG.read_text(encoding="utf-8")) or {}
|
||||
bundles = {b["bundle_code"]: b for b in catalog.get("bundles") or []}
|
||||
bundle = bundles.get("bundle.workspace.starter")
|
||||
if not bundle:
|
||||
fail("bundle.workspace.starter missing from catalog")
|
||||
print("Registry Validation: bundle.workspace.starter PASS")
|
||||
|
||||
services = {s["service_identifier"] for s in bundle.get("services") or []}
|
||||
if not REQUIRED_PRODUCTS.issubset(services):
|
||||
fail(f"bundle products {services} missing {REQUIRED_PRODUCTS - services}")
|
||||
prices = {
|
||||
p.get("pricing_item_code")
|
||||
for p in bundle.get("pricing_refs") or []
|
||||
if isinstance(p, dict)
|
||||
}
|
||||
if prices != REQUIRED_PRICES:
|
||||
fail(f"pricing refs {prices} != {REQUIRED_PRICES}")
|
||||
print("Pricing Validation: 3 workspace prices PASS")
|
||||
|
||||
caps = set(bundle.get("capability_codes") or [])
|
||||
if not REQUIRED_CAPS.issubset(caps):
|
||||
fail(f"capabilities missing {REQUIRED_CAPS - caps}")
|
||||
print("Cross Reference: workspace capabilities PASS")
|
||||
|
||||
seed = SEED.read_text(encoding="utf-8")
|
||||
if '"workspace": ("فضای کاری"' not in seed and '"workspace": ("فضای کاری", "/workspace"' not in seed:
|
||||
if '"workspace":' not in seed or "فضای کاری" not in seed:
|
||||
fail("workspace product not in seed")
|
||||
print("Registry Validation: workspace product PASS")
|
||||
|
||||
for item in REQUIRED_TYPES | REQUIRED_RULES | REQUIRED_PRICES:
|
||||
if item not in seed:
|
||||
fail(f"{item} missing from seed")
|
||||
print("Recommendation Validation: 5 workspace rules + 5 business types PASS")
|
||||
|
||||
tree = ast.parse(seed)
|
||||
print("Validation PASS")
|
||||
print(f"ast_ok={bool(tree)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@ -290,3 +290,35 @@ bundles:
|
||||
- { pricing_item_code: price.platform.trial.14d, role: trial }
|
||||
visibility: { public_catalog: true, admin_only: false, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: false, grandfathered: false }
|
||||
|
||||
- bundle_code: bundle.workspace.starter
|
||||
display_name: فضای کاری — نسخه شروع
|
||||
status: active
|
||||
version_code: "2026.07.1"
|
||||
business_types:
|
||||
- advertising_agency
|
||||
- content_team
|
||||
- software_company
|
||||
- marketing_company
|
||||
- team_freelancer
|
||||
services:
|
||||
- { service_identifier: workspace, required: true }
|
||||
- { service_identifier: communication, required: true }
|
||||
- { service_identifier: crm, required: true }
|
||||
- { service_identifier: accounting, required: true }
|
||||
capability_codes:
|
||||
- workspace.projects
|
||||
- workspace.tasks
|
||||
- workspace.kanban
|
||||
- workspace.calendar
|
||||
- workspace.content
|
||||
- workspace.approvals
|
||||
- workspace.comments
|
||||
- workspace.mentions
|
||||
- workspace.analytics
|
||||
pricing_refs:
|
||||
- { pricing_item_code: price.workspace.starter.monthly, role: primary }
|
||||
- { pricing_item_code: price.workspace.business.monthly, role: upgrade }
|
||||
- { pricing_item_code: price.workspace.enterprise, role: enterprise }
|
||||
visibility: { public_catalog: true, admin_only: false, hidden: false }
|
||||
lifecycle: { sellable: true, renewable: true, grandfathered: false }
|
||||
|
||||
@ -187,8 +187,8 @@ export function buildSetupChecklist(
|
||||
const workspaceActions =
|
||||
products.length === 0
|
||||
? [
|
||||
{ id: "workspace", label: "Workspace", href: "/dashboard", required: true },
|
||||
{ id: "billing", label: "Billing", href: "/billing", required: false },
|
||||
{ id: "workspace", label: "فضای کاری", href: "/workspace", required: true },
|
||||
{ id: "billing", label: "صورتحساب", href: "/billing", required: false },
|
||||
]
|
||||
: [];
|
||||
|
||||
|
||||
@ -445,6 +445,17 @@ const TOKEN_LABELS: Readonly<Record<string, string>> = {
|
||||
website: "وبسایت",
|
||||
experience: "وبسایت و تجربه دیجیتال",
|
||||
crm: "مدیریت ارتباط با مشتری",
|
||||
workspace: "فضای کاری",
|
||||
projects: "پروژهها",
|
||||
tasks: "وظایف",
|
||||
kanban: "کانبان",
|
||||
calendar: "تقویم",
|
||||
content: "محتوا",
|
||||
approvals: "تأییدیهها",
|
||||
comments: "نظرات",
|
||||
mentions: "منشن",
|
||||
analytics: "گزارشها",
|
||||
business: "کسبوکار",
|
||||
inventory: "انبارداری",
|
||||
pos: "صندوق فروش",
|
||||
communication: "ارتباط با مشتریان",
|
||||
@ -810,7 +821,7 @@ export function looksTechnical(value: string): boolean {
|
||||
|
||||
export function formatMoney(amount?: number | null, currency?: string | null): string {
|
||||
if (amount == null) return "استعلام قیمت";
|
||||
const unit = !currency || /irr|rial/i.test(currency) ? "ریال" : enumLabel(currency);
|
||||
const unit = !currency || /irr|rial|toman/i.test(currency) ? "تومان" : enumLabel(currency);
|
||||
return `${new Intl.NumberFormat("fa-IR").format(amount)} ${unit}`;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user