TorbatYar/frontend/scripts/_gen_payment_features.py
Mortezakoohjani 0d424c500a feat(platform): complete commercial runtime consolidation
Unify commercial runtime ownership across backend and frontend so platform, experience, and hospitality modules use the shared commercial source of truth.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 11:11:53 +03:30

159 lines
7.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Generate payment feature pages + App Router routes
from pathlib import Path
ROOT = Path(r"F:/TorbatYar/frontend")
MOD = ROOT / "modules" / "payment" / "features"
APP = ROOT / "app" / "payment"
# Feature implementations - compact but real API wired
FEATURES = {}
FEATURES["hub"] = r'''"use client";
import Link from "next/link";
import { CreditCard, LayoutDashboard, Wallet, Plug, Building2, Activity } from "lucide-react";
import { usePaymentCapabilities, usePaymentHealth } from "@/modules/payment/hooks/usePaymentCapabilities";
import { PaymentPageError, PaymentPageLoader } from "@/modules/payment/pages/shared";
const LINKS = [
{ href: "/payment/dashboard", label: "داشبورد اجرایی", icon: LayoutDashboard },
{ href: "/payment/requests", label: "درخواست‌های پرداخت", icon: Wallet },
{ href: "/payment/psp", label: "مدیریت درگاه", icon: Plug },
{ href: "/payment/merchants", label: "پذیرندگان", icon: Building2 },
{ href: "/payment/monitoring", label: "مانیتورینگ", icon: Activity },
];
export function PaymentHub() {
const health = usePaymentHealth();
const caps = usePaymentCapabilities();
if (health.isLoading) return <PaymentPageLoader />;
if (health.isError) return <PaymentPageError error={health.error} onRetry={() => health.refetch()} />;
return (
<div className="space-y-6">
<div className="rounded-2xl border border-[var(--border)] bg-[var(--surface)] p-6">
<div className="flex items-center gap-3">
<CreditCard className="h-8 w-8 text-teal-600" />
<div>
<h1 className="text-2xl font-semibold text-secondary">تربت‌پی</h1>
<p className="text-sm text-[var(--muted)]">پلتفرم پرداخت سازمانی — MVP تا فاز ۱۴.۵</p>
</div>
</div>
<div className="mt-4 flex flex-wrap gap-3 text-xs text-[var(--muted)]">
<span>وضعیت سرویس: {health.data?.status}</span>
<span>نسخه: {health.data?.version}</span>
<span>فاز: {caps.data?.phase ?? ""}</span>
<span>باندل‌ها: {(caps.data?.active_bundles ?? []).join(", ") || "هیچ"}</span>
</div>
</div>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{LINKS.map((l) => {
const Icon = l.icon;
return (
<Link key={l.href} href={l.href} className="rounded-xl border border-[var(--border)] bg-[var(--surface)] p-4 hover:border-teal-600/50">
<Icon className="mb-2 h-5 w-5 text-teal-600" />
<p className="font-medium text-secondary">{l.label}</p>
</Link>
);
})}
</div>
</div>
);
}
'''
FEATURES["dashboard"] = r'''"use client";
import { useMemo } from "react";
import { useQueries } from "@tanstack/react-query";
import { useTenantId } from "@/hooks/useTenantId";
import { paymentApi } from "@/modules/payment/services/payment-api";
import { PaymentBarChart, PaymentDonutChart, PaymentStatCards } from "@/modules/payment/components/PaymentCharts";
import { PaymentPageError, PaymentPageLoader } from "@/modules/payment/pages/shared";
import { usePaymentCapabilities } from "@/modules/payment/hooks/usePaymentCapabilities";
export function PaymentDashboard() {
const { tenantId } = useTenantId();
const caps = usePaymentCapabilities();
const [tx, req, audit] = useQueries({
queries: [
{ queryKey: ["payment", tenantId, "tx"], queryFn: () => paymentApi.transactions.list(tenantId!), enabled: !!tenantId },
{ queryKey: ["payment", tenantId, "req"], queryFn: () => paymentApi.requests.list(tenantId!), enabled: !!tenantId },
{ queryKey: ["payment", tenantId, "audit"], queryFn: () => paymentApi.audit.list(tenantId!), enabled: !!tenantId },
],
});
const loading = !tenantId || tx.isLoading || req.isLoading;
const err = tx.error || req.error;
const stats = useMemo(() => {
const requests = req.data ?? [];
const txs = tx.data ?? [];
const paid = requests.filter((r) => r.status === "paid").length;
const failed = requests.filter((r) => r.status === "failed").length;
const volume = txs.reduce((s, t) => s + Number(t.amount_minor ?? 0), 0);
const statusDist = Object.entries(
requests.reduce<Record<string, number>>((acc, r) => {
const k = String(r.status ?? "unknown");
acc[k] = (acc[k] ?? 0) + 1;
return acc;
}, {})
).map(([name, value]) => ({ name, value }));
return { paid, failed, volume, totalReq: requests.length, totalTx: txs.length, statusDist, recent: (audit.data ?? []).slice(0, 8) };
}, [req.data, tx.data, audit.data]);
if (loading) return <PaymentPageLoader />;
if (err) return <PaymentPageError error={err} onRetry={() => { tx.refetch(); req.refetch(); }} />;
return (
<div className="space-y-6">
<div>
<h1 className="text-xl font-semibold text-secondary">داشبورد اجرایی تربت‌پی</h1>
<p className="text-sm text-[var(--muted)]">داده‌ها از API واقعی تراکنش، درخواست و حسابرسی</p>
</div>
<PaymentStatCards
items={[
{ label: "درخواست‌ها", value: stats.totalReq },
{ label: "پرداخت‌شده", value: stats.paid },
{ label: "ناموفق", value: stats.failed },
{ label: "حجم (minor)", value: stats.volume.toLocaleString("fa-IR"), hint: `باندل‌ها: ${(caps.data?.active_bundles ?? []).join(", ") || ""}` },
]}
/>
<div className="grid gap-4 lg:grid-cols-2">
<div className="rounded-xl border border-[var(--border)] bg-[var(--surface)] p-4">
<h2 className="mb-2 text-sm font-medium text-secondary">توزیع وضعیت درخواست‌ها</h2>
<PaymentDonutChart data={stats.statusDist} />
</div>
<div className="rounded-xl border border-[var(--border)] bg-[var(--surface)] p-4">
<h2 className="mb-2 text-sm font-medium text-secondary">حجم تراکنش‌ها بر اساس ارز</h2>
<PaymentBarChart
data={Object.entries(
(tx.data ?? []).reduce<Record<string, number>>((acc, t) => {
const c = String(t.currency ?? "IRR");
acc[c] = (acc[c] ?? 0) + Number(t.amount_minor ?? 0);
return acc;
}, {})
).map(([name, value]) => ({ name, value }))}
/>
</div>
</div>
<div className="rounded-xl border border-[var(--border)] bg-[var(--surface)] p-4">
<h2 className="mb-3 text-sm font-medium text-secondary">فعالیت اخیر (حسابرسی)</h2>
<ul className="space-y-2 text-sm">
{stats.recent.length === 0 ? (
<li className="text-[var(--muted)]">فعالیتی ثبت نشده</li>
) : (
stats.recent.map((a, i) => (
<li key={String(a.id ?? i)} className="flex justify-between border-b border-[var(--border)] py-2">
<span>{String(a.action)} — {String(a.entity_type)}</span>
<span className="text-xs text-[var(--muted)]">{a.created_at ? new Date(String(a.created_at)).toLocaleString("fa-IR") : ""}</span>
</li>
))
)}
</ul>
</div>
</div>
);
}
'''
# Write generator that creates list-based features and remaining pages
print("writing generator body...")