Complete Accounting sidebar modules with ops CRUD, BFF proxy, and treasury APIs.

Wire nested nav pages for phases 5.1-5.11 to real list/create endpoints, fix COA import connectivity via same-origin proxy, and add operational migration.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Mortezakoohjani 2026-07-24 16:10:06 +03:30
parent 12c8615615
commit 7d66932da8
119 changed files with 3976 additions and 135 deletions

View File

@ -0,0 +1,73 @@
"""Alembic: operational docs, inventory items, warehouses."""
from __future__ import annotations
import sqlalchemy as sa
from alembic import op
from app.models.types import GUID
revision = "0003_operational"
down_revision = "0002_phases_57_511"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.create_table(
"business_documents",
sa.Column("id", GUID(), primary_key=True),
sa.Column("tenant_id", GUID(), nullable=False),
sa.Column("module", sa.String(40), nullable=False),
sa.Column("doc_type", sa.String(50), nullable=False),
sa.Column("number", sa.String(50), nullable=False),
sa.Column("doc_date", sa.Date(), nullable=False),
sa.Column("party_name", sa.String(255), nullable=True),
sa.Column("party_id", GUID(), nullable=True),
sa.Column("amount", sa.Numeric(18, 4), nullable=False, server_default="0"),
sa.Column("status", sa.String(30), nullable=False, server_default="draft"),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("meta_json", sa.Text(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.UniqueConstraint("tenant_id", "module", "number", name="uq_biz_doc_tenant_module_number"),
)
op.create_index(
"ix_biz_doc_tenant_module_type",
"business_documents",
["tenant_id", "module", "doc_type"],
)
op.create_table(
"inventory_items",
sa.Column("id", GUID(), primary_key=True),
sa.Column("tenant_id", GUID(), nullable=False),
sa.Column("code", sa.String(50), nullable=False),
sa.Column("name", sa.String(255), nullable=False),
sa.Column("item_type", sa.String(30), nullable=False, server_default="goods"),
sa.Column("unit", sa.String(30), nullable=False, server_default="عدد"),
sa.Column("quantity_on_hand", sa.Numeric(18, 4), nullable=False, server_default="0"),
sa.Column("unit_cost", sa.Numeric(18, 4), nullable=False, server_default="0"),
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.UniqueConstraint("tenant_id", "code", name="uq_inventory_item_tenant_code"),
)
op.create_table(
"warehouses",
sa.Column("id", GUID(), primary_key=True),
sa.Column("tenant_id", GUID(), nullable=False),
sa.Column("code", sa.String(50), nullable=False),
sa.Column("name", sa.String(255), nullable=False),
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.UniqueConstraint("tenant_id", "code", name="uq_warehouse_tenant_code"),
)
def downgrade() -> None:
op.drop_table("warehouses")
op.drop_table("inventory_items")
op.drop_index("ix_biz_doc_tenant_module_type", table_name="business_documents")
op.drop_table("business_documents")

View File

@ -23,7 +23,7 @@ __all__ = [
def get_pagination(
page: int = Query(default=1, ge=1),
page_size: int = Query(default=20, ge=1, le=100),
page_size: int = Query(default=20, ge=1, le=500),
) -> PaginationParams:
return PaginationParams(page=page, page_size=page_size)

View File

@ -7,6 +7,7 @@ from app.api.v1 import (
fixed_assets,
health,
ledger,
operational,
payroll,
posting,
purchase_inventory,
@ -31,3 +32,4 @@ api_router.include_router(payroll.router, prefix="/payroll", tags=["payroll"])
api_router.include_router(reporting.router, prefix="/reporting", tags=["reporting"])
api_router.include_router(compliance.router, prefix="/compliance", tags=["compliance"])
api_router.include_router(setup.router, prefix="/setup", tags=["setup"])
api_router.include_router(operational.router, prefix="/ops", tags=["ops"])

View File

@ -105,6 +105,25 @@ async def create_audit_record(
return {"id": str(record.id)}
@router.get("/policies")
async def list_compliance_policies(
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
rows = await CompliancePolicyRepo(db).list_by_tenant(tenant_id, limit=200)
return [
{
"id": str(p.id),
"code": p.code,
"name": p.name,
"policy_type": p.policy_type,
"is_active": getattr(p, "is_active", True),
}
for p in rows
]
@router.post("/policies", status_code=status.HTTP_201_CREATED)
async def create_compliance_policy(
body: CompliancePolicyCreate,

View File

@ -0,0 +1,218 @@
"""Operational documents + inventory masters API."""
from __future__ import annotations
from datetime import date
from decimal import Decimal
from uuid import UUID
from fastapi import APIRouter, Depends, Query, status
from pydantic import BaseModel, Field
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.deps import get_db, require_tenant
from app.core.security import get_current_user
from app.models.operational import BusinessDocument, InventoryItem, Warehouse
from app.repositories.base import TenantBaseRepository
from shared.exceptions import NotFoundError
from shared.security import CurrentUser
router = APIRouter()
class BizDocCreate(BaseModel):
module: str = Field(max_length=40)
doc_type: str = Field(max_length=50)
number: str = Field(max_length=50)
doc_date: date
party_name: str | None = None
party_id: UUID | None = None
amount: Decimal = Decimal("0")
status: str = "draft"
description: str | None = None
meta_json: str | None = None
class BizDocUpdate(BaseModel):
party_name: str | None = None
amount: Decimal | None = None
status: str | None = None
description: str | None = None
doc_date: date | None = None
meta_json: str | None = None
class BizDocRead(BaseModel):
id: UUID
tenant_id: UUID
module: str
doc_type: str
number: str
doc_date: date
party_name: str | None
amount: Decimal
status: str
description: str | None
model_config = {"from_attributes": True}
class ItemCreate(BaseModel):
code: str
name: str
item_type: str = "goods"
unit: str = "عدد"
quantity_on_hand: Decimal = Decimal("0")
unit_cost: Decimal = Decimal("0")
class ItemRead(BaseModel):
id: UUID
code: str
name: str
item_type: str
unit: str
quantity_on_hand: Decimal
unit_cost: Decimal
is_active: bool
model_config = {"from_attributes": True}
class WarehouseCreate(BaseModel):
code: str
name: str
class WarehouseRead(BaseModel):
id: UUID
code: str
name: str
is_active: bool
model_config = {"from_attributes": True}
class BizDocRepo(TenantBaseRepository[BusinessDocument]):
model = BusinessDocument
class ItemRepo(TenantBaseRepository[InventoryItem]):
model = InventoryItem
class WarehouseRepo(TenantBaseRepository[Warehouse]):
model = Warehouse
@router.get("/documents", response_model=list[BizDocRead])
async def list_documents(
module: str | None = Query(default=None),
doc_type: str | None = Query(default=None),
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
stmt = select(BusinessDocument).where(BusinessDocument.tenant_id == tenant_id)
if module:
stmt = stmt.where(BusinessDocument.module == module)
if doc_type:
stmt = stmt.where(BusinessDocument.doc_type == doc_type)
stmt = stmt.order_by(BusinessDocument.doc_date.desc()).limit(200)
result = await db.execute(stmt)
return list(result.scalars().all())
@router.post("/documents", response_model=BizDocRead, status_code=status.HTTP_201_CREATED)
async def create_document(
body: BizDocCreate,
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
entity = BusinessDocument(tenant_id=tenant_id, **body.model_dump())
await BizDocRepo(db).add(entity)
await db.commit()
await db.refresh(entity)
return entity
@router.patch("/documents/{doc_id}", response_model=BizDocRead)
async def update_document(
doc_id: UUID,
body: BizDocUpdate,
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
repo = BizDocRepo(db)
entity = await repo.get(tenant_id, doc_id)
if entity is None:
raise NotFoundError("سند یافت نشد", error_code="document_not_found")
for k, v in body.model_dump(exclude_unset=True).items():
setattr(entity, k, v)
await db.commit()
await db.refresh(entity)
return entity
@router.post("/documents/{doc_id}/confirm", response_model=BizDocRead)
async def confirm_document(
doc_id: UUID,
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
entity = await BizDocRepo(db).get(tenant_id, doc_id)
if entity is None:
raise NotFoundError("سند یافت نشد", error_code="document_not_found")
entity.status = "confirmed"
await db.commit()
await db.refresh(entity)
return entity
@router.get("/items", response_model=list[ItemRead])
async def list_items(
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
return await ItemRepo(db).list_by_tenant(tenant_id, limit=200)
@router.post("/items", response_model=ItemRead, status_code=status.HTTP_201_CREATED)
async def create_item(
body: ItemCreate,
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
entity = InventoryItem(tenant_id=tenant_id, **body.model_dump())
await ItemRepo(db).add(entity)
await db.commit()
await db.refresh(entity)
return entity
@router.get("/warehouses", response_model=list[WarehouseRead])
async def list_warehouses(
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
return await WarehouseRepo(db).list_by_tenant(tenant_id, limit=100)
@router.post("/warehouses", response_model=WarehouseRead, status_code=status.HTTP_201_CREATED)
async def create_warehouse(
body: WarehouseCreate,
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
entity = Warehouse(tenant_id=tenant_id, **body.model_dump())
await WarehouseRepo(db).add(entity)
await db.commit()
await db.refresh(entity)
return entity

View File

@ -204,3 +204,256 @@ async def update_cash_box(
await db.commit()
await db.refresh(entity)
return CashBoxRead.model_validate(entity)
# ── Cheques / transfers / receipts / payments / reconciliation ───────────────
from app.models.treasury import Cheque, PaymentVoucher, ReceiptVoucher, Reconciliation, Transfer
from app.models.types import ChequeStatus, ReconciliationStatus
class ChequeRepo(TenantBaseRepository[Cheque]):
model = Cheque
class TransferRepo(TenantBaseRepository[Transfer]):
model = Transfer
class ReceiptRepo(TenantBaseRepository[ReceiptVoucher]):
model = ReceiptVoucher
class PaymentRepo(TenantBaseRepository[PaymentVoucher]):
model = PaymentVoucher
class ReconciliationRepo(TenantBaseRepository[Reconciliation]):
model = Reconciliation
class ChequeCreate(BaseModel):
cheque_number: str
amount: Decimal
issue_date: date
due_date: date | None = None
is_incoming: bool = True
payee: str | None = None
bank_account_id: UUID | None = None
status: ChequeStatus = ChequeStatus.RECEIVED
class TransferCreate(BaseModel):
transfer_date: date
amount: Decimal
from_type: str
from_id: UUID
to_type: str
to_id: UUID
description: str | None = None
class ReceiptCreate(BaseModel):
receipt_number: str
receipt_date: date
amount: Decimal
payment_method: str = "cash"
cash_box_id: UUID | None = None
bank_account_id: UUID | None = None
description: str | None = None
class PaymentCreate(BaseModel):
payment_number: str
payment_date: date
amount: Decimal
payment_method: str = "cash"
cash_box_id: UUID | None = None
bank_account_id: UUID | None = None
description: str | None = None
class ReconciliationCreate(BaseModel):
bank_account_id: UUID
statement_date: date
statement_balance: Decimal
book_balance: Decimal
@router.get("/cheques")
async def list_cheques(
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
rows = await ChequeRepo(db).list_by_tenant(tenant_id, limit=200)
return [
{
"id": str(c.id),
"cheque_number": c.cheque_number,
"amount": str(c.amount),
"issue_date": c.issue_date.isoformat(),
"due_date": c.due_date.isoformat() if c.due_date else None,
"status": c.status.value,
"is_incoming": c.is_incoming,
"payee": c.payee,
}
for c in rows
]
@router.post("/cheques", status_code=status.HTTP_201_CREATED)
async def create_cheque(
body: ChequeCreate,
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
entity = Cheque(tenant_id=tenant_id, **body.model_dump())
await ChequeRepo(db).add(entity)
await db.commit()
return {"id": str(entity.id), "cheque_number": entity.cheque_number}
@router.get("/transfers")
async def list_transfers(
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
rows = await TransferRepo(db).list_by_tenant(tenant_id, limit=200)
return [
{
"id": str(t.id),
"transfer_date": t.transfer_date.isoformat(),
"amount": str(t.amount),
"from_type": t.from_type,
"from_id": str(t.from_id),
"to_type": t.to_type,
"to_id": str(t.to_id),
"description": t.description,
}
for t in rows
]
@router.post("/transfers", status_code=status.HTTP_201_CREATED)
async def create_transfer(
body: TransferCreate,
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
entity = Transfer(tenant_id=tenant_id, **body.model_dump())
await TransferRepo(db).add(entity)
await db.commit()
return {"id": str(entity.id)}
@router.get("/receipts")
async def list_receipts(
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
rows = await ReceiptRepo(db).list_by_tenant(tenant_id, limit=200)
return [
{
"id": str(r.id),
"receipt_number": r.receipt_number,
"receipt_date": r.receipt_date.isoformat(),
"amount": str(r.amount),
"payment_method": r.payment_method,
"description": r.description,
}
for r in rows
]
@router.post("/receipts", status_code=status.HTTP_201_CREATED)
async def create_receipt(
body: ReceiptCreate,
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
entity = ReceiptVoucher(tenant_id=tenant_id, **body.model_dump())
await ReceiptRepo(db).add(entity)
await db.commit()
return {"id": str(entity.id)}
@router.get("/payments")
async def list_payments(
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
rows = await PaymentRepo(db).list_by_tenant(tenant_id, limit=200)
return [
{
"id": str(p.id),
"payment_number": p.payment_number,
"payment_date": p.payment_date.isoformat(),
"amount": str(p.amount),
"payment_method": p.payment_method,
"description": p.description,
}
for p in rows
]
@router.post("/payments", status_code=status.HTTP_201_CREATED)
async def create_payment(
body: PaymentCreate,
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
entity = PaymentVoucher(tenant_id=tenant_id, **body.model_dump())
await PaymentRepo(db).add(entity)
await db.commit()
return {"id": str(entity.id)}
@router.get("/reconciliations")
async def list_reconciliations(
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
rows = await ReconciliationRepo(db).list_by_tenant(tenant_id, limit=100)
return [
{
"id": str(r.id),
"bank_account_id": str(r.bank_account_id),
"statement_date": r.statement_date.isoformat(),
"statement_balance": str(r.statement_balance),
"book_balance": str(r.book_balance),
"difference": str(r.difference),
"status": r.status.value,
}
for r in rows
]
@router.post("/reconciliations", status_code=status.HTTP_201_CREATED)
async def create_reconciliation(
body: ReconciliationCreate,
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
):
diff = body.statement_balance - body.book_balance
entity = Reconciliation(
tenant_id=tenant_id,
bank_account_id=body.bank_account_id,
statement_date=body.statement_date,
statement_balance=body.statement_balance,
book_balance=body.book_balance,
difference=diff,
status=ReconciliationStatus.PENDING,
)
await ReconciliationRepo(db).add(entity)
await db.commit()
return {"id": str(entity.id), "difference": str(diff)}

View File

@ -140,3 +140,8 @@ from app.models.treasury import ( # noqa: F401
Transfer,
TreasurySettings,
)
from app.models.operational import ( # noqa: F401
BusinessDocument,
InventoryItem,
Warehouse,
)

View File

@ -0,0 +1,63 @@
"""Phase ops — lightweight business documents for Sales/Purchase/Inventory UI."""
from __future__ import annotations
import uuid
from datetime import date
from decimal import Decimal
from sqlalchemy import Boolean, Date, Index, Numeric, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.models.base import TenantMixin, TimestampMixin, UUIDPrimaryKeyMixin
from app.models.types import GUID
class BusinessDocument(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin):
"""Generic operational document used by Sales / Purchase / Inventory screens."""
__tablename__ = "business_documents"
module: Mapped[str] = mapped_column(String(40), nullable=False)
doc_type: Mapped[str] = mapped_column(String(50), nullable=False)
number: Mapped[str] = mapped_column(String(50), nullable=False)
doc_date: Mapped[date] = mapped_column(Date, nullable=False)
party_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
party_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), default=Decimal("0"), nullable=False)
status: Mapped[str] = mapped_column(String(30), default="draft", nullable=False)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
meta_json: Mapped[str | None] = mapped_column(Text, nullable=True)
__table_args__ = (
UniqueConstraint("tenant_id", "module", "number", name="uq_biz_doc_tenant_module_number"),
Index("ix_biz_doc_tenant_module_type", "tenant_id", "module", "doc_type"),
)
class InventoryItem(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin):
__tablename__ = "inventory_items"
code: Mapped[str] = mapped_column(String(50), nullable=False)
name: Mapped[str] = mapped_column(String(255), nullable=False)
item_type: Mapped[str] = mapped_column(String(30), default="goods", nullable=False)
unit: Mapped[str] = mapped_column(String(30), default="عدد", nullable=False)
quantity_on_hand: Mapped[Decimal] = mapped_column(Numeric(18, 4), default=Decimal("0"), nullable=False)
unit_cost: Mapped[Decimal] = mapped_column(Numeric(18, 4), default=Decimal("0"), nullable=False)
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
__table_args__ = (
UniqueConstraint("tenant_id", "code", name="uq_inventory_item_tenant_code"),
)
class Warehouse(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin):
__tablename__ = "warehouses"
code: Mapped[str] = mapped_column(String(50), nullable=False)
name: Mapped[str] = mapped_column(String(255), nullable=False)
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
__table_args__ = (
UniqueConstraint("tenant_id", "code", name="uq_warehouse_tenant_code"),
)

View File

@ -283,6 +283,11 @@ async def import_coa_template(
status_code=422,
error_code="parent_missing",
)
existing_acc = await account_repo.get_by_code(tenant_id, item.code)
if existing_acc is not None:
# Reuse existing account code so re-import / overlapping COA does not crash
code_to_id[item.code] = existing_acc.id
continue
acc = Account(
tenant_id=tenant_id,
chart_id=chart.id,

View File

@ -11,7 +11,7 @@ from pydantic import BaseModel, Field
T = TypeVar("T")
DEFAULT_PAGE_SIZE = 20
MAX_PAGE_SIZE = 100
MAX_PAGE_SIZE = 500
class PaginationParams(BaseModel):

View File

@ -189,6 +189,7 @@ services:
NEXT_PUBLIC_KEYCLOAK_CLIENT_ID: ${NEXT_PUBLIC_KEYCLOAK_CLIENT_ID:-superapp-frontend}
NEXT_PUBLIC_PLATFORM_BASE_DOMAIN: ${NEXT_PUBLIC_PLATFORM_BASE_DOMAIN:-torbatyar.ir}
NEXT_PUBLIC_ACCOUNTING_API_URL: ${NEXT_PUBLIC_ACCOUNTING_API_URL:-http://localhost:8002}
ACCOUNTING_SERVICE_URL: ${ACCOUNTING_SERVICE_URL:-http://accounting-service:8002}
# polling برای hot-reload پایدار روی volume mount ویندوز
WATCHPACK_POLLING: "true"
CHOKIDAR_USEPOLLING: "true"

View File

@ -0,0 +1,7 @@
"use client";
import { BlockedModulePage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return <BlockedModulePage title="تحلیل هوشمند" />;
}

View File

@ -0,0 +1,7 @@
"use client";
import { BlockedModulePage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return <BlockedModulePage title="کشف ناهنجاری" />;
}

View File

@ -0,0 +1,7 @@
"use client";
import { BlockedModulePage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return <BlockedModulePage title="تحلیل اسناد" />;
}

View File

@ -0,0 +1,7 @@
"use client";
import { BlockedModulePage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return <BlockedModulePage title="پیش‌بینی‌ها" />;
}

View File

@ -0,0 +1,7 @@
"use client";
import { BlockedModulePage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return <BlockedModulePage title="دستیار حسابداری" />;
}

View File

@ -0,0 +1,7 @@
"use client";
import { BlockedModulePage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return <BlockedModulePage title="پرسش و پاسخ" />;
}

View File

@ -0,0 +1,7 @@
"use client";
import { BlockedModulePage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return <BlockedModulePage title="پیشنهادهای هوشمند" />;
}

View File

@ -0,0 +1,7 @@
"use client";
import { BlockedModulePage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return <BlockedModulePage title="تطبیق هوشمند" />;
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="استهلاک انباشته"
module="assets"
docType="accumulated"
/>
);
}

View File

@ -0,0 +1,5 @@
import { redirect } from "next/navigation";
export default function Page() {
redirect("/accounting/assets");
}

View File

@ -0,0 +1,5 @@
import { redirect } from "next/navigation";
export default function Page() {
redirect("/accounting/assets");
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="معرفی و فروش دارایی"
module="assets"
docType="dispose"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="برنامه استهلاک"
module="assets"
docType="schedule"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="انتقال دارایی"
module="assets"
docType="transfer"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="ارزش‌گذاری دارایی"
module="assets"
docType="valuation"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="کنترل بودجه"
module="budget"
docType="control"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="تعریف بودجه"
module="budget"
docType="definition"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="پیش‌بینی مالی"
module="budget"
docType="forecast"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="بودجه‌ریزی عملیاتی"
module="budget"
docType="operational"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="مقایسه عملکرد"
module="budget"
docType="performance"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="گزارش بودجه"
module="budget"
docType="report"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="سناریوهای مالی"
module="budget"
docType="scenario"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="تحلیل انحراف بودجه"
module="budget"
docType="variance"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="گردش تأیید"
module="compliance"
docType="approval"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="کنترل‌های داخلی"
module="compliance"
docType="control"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="سیاست‌ها و قوانین"
module="compliance"
docType="policy"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="اسناد و سوابق"
module="compliance"
docType="record"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="مدیریت ریسک"
module="compliance"
docType="risk"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="تفکیک وظایف (SoD)"
module="compliance"
docType="sod"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="گزارش تخلفات"
module="compliance"
docType="violation"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="دسته‌بندی اسناد"
module="documents"
docType="category"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="اسناد مالی"
module="documents"
docType="financial"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="اسناد پرسنلی"
module="documents"
docType="hr"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="اسناد خرید"
module="documents"
docType="purchase"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="نگهداری اسناد"
module="documents"
docType="retention"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="اسناد فروش"
module="documents"
docType="sales"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="جستجوی اسناد"
module="documents"
docType="search"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="نسخه‌های سند"
module="documents"
docType="version"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="اتصال بانک"
module="integration"
docType="bank"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="اتصال CRM"
module="integration"
docType="crm"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="درگاه پرداخت"
module="integration"
docType="gateway"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="ورود / خروج داده"
module="integration"
docType="import_export"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="اتصال انبار"
module="integration"
docType="inventory"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="اتصال حقوق"
module="integration"
docType="payroll"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="گزارش یکپارچگی"
module="integration"
docType="report"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="وب‌سرویس‌ها"
module="integration"
docType="webhook"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="تعدیل موجودی"
module="inventory"
docType="adjustment"
/>
);
}

View File

@ -0,0 +1,7 @@
"use client";
import { InventoryItemsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return <InventoryItemsPage />;
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="گردش کالا"
module="inventory"
docType="movement"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="رسید ورود و خروج"
module="inventory"
docType="receipt_issue"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="گزارش‌های انبار"
module="inventory"
docType="report"
/>
);
}

View File

@ -0,0 +1,7 @@
"use client";
import { WarehousesPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return <WarehousesPage />;
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="انتقال انبار"
module="inventory"
docType="transfer"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="ارزش‌گذاری موجودی"
module="inventory"
docType="valuation"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="فعالیت کاربران"
module="monitoring"
docType="activity"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="هشدارها"
module="monitoring"
docType="alert"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="وضعیت سرویس"
module="monitoring"
docType="health"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="شاخص‌های کلیدی"
module="monitoring"
docType="kpi"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="داشبورد نظارتی"
module="monitoring"
docType="dashboard"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="عملکرد سیستم"
module="monitoring"
docType="performance"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="عملیات اخیر"
module="monitoring"
docType="recent"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="تسهیم هزینه حقوق"
module="payroll"
docType="allocation"
/>
);
}

View File

@ -0,0 +1,5 @@
import { redirect } from "next/navigation";
export default function Page() {
redirect("/accounting/payroll");
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="تعریف قرارداد"
module="payroll"
docType="contract"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="کارمندان"
module="payroll"
docType="employee"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="اقلام حقوق و مزایا"
module="payroll"
docType="pay_item"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="پرداخت حقوق"
module="payroll"
docType="payment"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="گزارش‌های حقوق"
module="payroll"
docType="report"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="ساختار حقوق"
module="payroll"
docType="structure"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="رسید کالا"
module="purchase"
docType="goods_receipt"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="فاکتور خرید"
module="purchase"
docType="invoice"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="سفارش خرید"
module="purchase"
docType="order"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="پیش‌پرداخت‌ها"
module="purchase"
docType="prepayment"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="درخواست خرید"
module="purchase"
docType="request"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="مرجوعی خرید"
module="purchase"
docType="return"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="تسویه با تأمین‌کننده"
module="purchase"
docType="settlement"
/>
);
}

View File

@ -1,11 +1,13 @@
"use client";
import { useMemo, useState } from "react";
import { useMemo, useState, Suspense } from "react";
import { useSearchParams } from "next/navigation";
import { useMutation, useQuery } from "@tanstack/react-query";
import { toast } from "sonner";
import { accountingApi } from "@/lib/accounting-api";
import { useTenantId } from "@/hooks/useTenantId";
import { formatMoney } from "@/lib/utils";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
import {
PageHeader,
Button,
@ -30,6 +32,14 @@ const LABELS: Record<ReportKind, string> = {
"income-statement": "سود و زیان",
};
const OPS_REPORTS: Record<string, { title: string; module: string; docType: string }> = {
"cash-flow": { title: "جریان وجوه نقد", module: "reports", docType: "cash_flow" },
equity: { title: "تغییرات حقوق صاحبان سهام", module: "reports", docType: "equity" },
subsidiary: { title: "گزارش معین", module: "reports", docType: "subsidiary" },
detail: { title: "گزارش تفصیلی", module: "reports", docType: "detail" },
analytical: { title: "گزارش تحلیلی", module: "reports", docType: "analytical" },
};
const FIELD_LABELS: Record<string, string> = {
total_debit: "جمع بدهکار",
total_credit: "جمع بستانکار",
@ -66,7 +76,7 @@ function formatCellValue(key: string, value: unknown): React.ReactNode {
return String(value);
}
export default function ReportsPage() {
function EngineReports({ initialKind }: { initialKind?: string }) {
const { tenantId } = useTenantId();
const [periodId, setPeriodId] = useState("");
const [result, setResult] = useState<{ kind: ReportKind; payload: { id: string; data: unknown } } | null>(
@ -105,10 +115,7 @@ export default function ReportsPage() {
accountingApi.reporting.export(tenantId!, result!.payload.id, format),
onSuccess: (data) => {
toast.success(`خروجی ${data.format} ثبت شد`);
if (data.file_path) {
toast.message(data.file_path);
}
// Download client-side JSON snapshot for immediate UX
if (data.file_path) toast.message(data.file_path);
if (result) {
const blob = new Blob([JSON.stringify(result.payload, null, 2)], {
type: "application/json",
@ -144,6 +151,13 @@ export default function ReportsPage() {
return <ErrorState message={periodsQ.error.message} onRetry={() => periodsQ.refetch()} />;
}
const highlight =
initialKind === "trial-balance" ||
initialKind === "balance-sheet" ||
initialKind === "income-statement"
? (initialKind as ReportKind)
: null;
return (
<div>
<PageHeader
@ -168,7 +182,7 @@ export default function ReportsPage() {
<Button
key={kind}
type="button"
variant="outline"
variant={highlight === kind ? "default" : "outline"}
disabled={runM.isPending || !periodId}
onClick={() => runM.mutate(kind)}
>
@ -233,3 +247,21 @@ export default function ReportsPage() {
</div>
);
}
function ReportsRouter() {
const sp = useSearchParams();
const type = sp.get("type") || "";
const ops = OPS_REPORTS[type];
if (ops) {
return <BusinessDocsPage title={ops.title} module={ops.module} docType={ops.docType} />;
}
return <EngineReports initialKind={type || undefined} />;
}
export default function ReportsPage() {
return (
<Suspense fallback={<LoadingState />}>
<ReportsRouter />
</Suspense>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="فاکتور فروش"
module="sales"
docType="invoice"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="فرصت‌های فروش"
module="sales"
docType="opportunity"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="سفارش فروش"
module="sales"
docType="order"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="پیش‌دریافت‌ها"
module="sales"
docType="pre_receipt"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="پیش‌فاکتور"
module="sales"
docType="proforma"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="مرجوعی فروش"
module="sales"
docType="return"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="تسویه حساب مشتریان"
module="sales"
docType="settlement"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="پشتیبان‌گیری"
module="settings"
docType="backup"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="اطلاعات شرکت"
module="settings"
docType="company"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="تنظیمات عمومی"
module="settings"
docType="general"
/>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { BusinessDocsPage } from "@/components/accounting/BusinessDocsPage";
export default function Page() {
return (
<BusinessDocsPage
title="تنظیمات مالیاتی"
module="settings"
docType="tax"
/>
);
}

View File

@ -49,7 +49,7 @@ const STEP_LINKS = {
has_base_currency: { href: "/accounting/currencies", label: "ارز پایه" },
has_fiscal_year: { href: "/accounting/fiscal", label: "سال مالی" },
has_fiscal_period: { href: "/accounting/fiscal", label: "دوره مالی" },
has_cash_box: { href: "/accounting/treasury", label: "صندوق" },
has_cash_box: { href: "/accounting/treasury/cash-boxes", label: "صندوق" },
has_customer: { href: "/accounting/customers", label: "مشتری" },
has_voucher: { href: "/accounting/vouchers", label: "سند حسابداری" },
} as const;

Some files were not shown because too many files have changed in this diff Show More