Files
2026-08-22 22:12:23 +05:30

210 lines
6.3 KiB
Python

from __future__ import annotations
from urllib.parse import urlencode
from fastapi import APIRouter, File, Form, Request, UploadFile
from fastapi.responses import RedirectResponse
from app.core.db.common import CommonSessionLocal
from app.core.security.csrf import get_or_create_csrf_token, validate_csrf
from app.core.templating import templates
from app.modules.accounting.sales_service import import_sales_file, list_sales, review_sales
from app.modules.accounting.sales_taxonomy import sales_income_natures
from app.modules.accounting.ui import _find_visible_client, _require_partner, _visible_clients
from app.modules.core.rbac.deps import get_user_permissions, get_user_roles
router = APIRouter(
prefix="/tools/accounting/sales-income",
tags=["accounting-sales-income-ui"],
)
def _go(client_id=0, message="", error="", **filters):
query = {}
if client_id:
query["client_id"] = client_id
if message:
query["message"] = message[:300]
if error:
query["error"] = error[:300]
for key, value in filters.items():
if value not in (None, "", "all"):
query[key] = value
return RedirectResponse(
"/tools/accounting/sales-income" + ("?" + urlencode(query) if query else ""),
status_code=303,
)
@router.get("")
def page(
request: Request,
client_id: int | None = None,
status: str = "all",
customer: str = "",
period: str = "",
page: int = 1,
per_page: int = 25,
message: str = "",
error: str = "",
):
db = CommonSessionLocal()
try:
user, denied = _require_partner(request, db, "accounting.learning.view")
if denied:
return denied
clients, scope = _visible_clients(db, request, user)
selected = next(
(client for client in clients if client_id and int(client.id) == int(client_id)),
None,
)
rows = []
total = 0
pages = 1
if selected:
rows, total, page, pages = list_sales(
db,
tenant_id=scope.tenant_id,
client_id=selected.id,
status=status,
customer=customer,
period=period,
page=page,
per_page=per_page,
)
return templates.TemplateResponse(
"modules/accounting/templates/accounting/sales_income.html",
{
"request": request,
"current_user": user,
"current_user_roles": get_user_roles(db, user.id),
"current_user_permissions": get_user_permissions(db, user.id),
"csrf_token": get_or_create_csrf_token(request),
"title": "Sales & Income",
"clients": clients,
"selected_client": selected,
"rows": rows,
"total": total,
"page": page,
"pages": pages,
"per_page": per_page,
"status_filter": status,
"customer_filter": customer,
"period_filter": period,
"natures": sales_income_natures(db, scope.tenant_id),
"message": message,
"error": error,
},
)
finally:
db.close()
@router.post("/import")
async def import_file(
request: Request,
client_id: int = Form(...),
source_type: str = Form("outward_import"),
csrf_token: str = Form(...),
file: UploadFile = File(...),
):
validate_csrf(request, csrf_token)
db = CommonSessionLocal()
try:
user, denied = _require_partner(request, db, "accounting.learning.manage")
if denied:
return denied
client, _, scope = _find_visible_client(db, request, user, client_id)
if not client:
return _go(error="Client is not visible.")
payload = await file.read()
if not payload:
raise ValueError("Uploaded Sales & Income file is empty.")
if len(payload) > 25 * 1024 * 1024:
raise ValueError("Sales & Income import file exceeds the 25 MB limit.")
result = import_sales_file(
db,
tenant_id=scope.tenant_id,
client_id=client.id,
filename=file.filename or "sales.xlsx",
payload=payload,
user_id=user.id,
source_type=(source_type or "outward_import").strip()[:30],
)
return _go(
client.id,
message=(
f"Imported {result['inserted']} sales/income row(s); "
f"{result['duplicates']} duplicate(s) skipped."
),
)
except Exception as exc:
db.rollback()
return _go(client_id, error=str(exc))
finally:
db.close()
@router.post("/{row_id}/review")
def review(
request: Request,
row_id: int,
client_id: int = Form(...),
final_nature_id: int = Form(...),
final_ledger_name: str = Form(""),
review_note: str = Form(""),
status: str = Form("all"),
customer: str = Form(""),
period: str = Form(""),
page: int = Form(1),
per_page: int = Form(25),
csrf_token: str = Form(...),
):
validate_csrf(request, csrf_token)
db = CommonSessionLocal()
try:
user, denied = _require_partner(request, db, "accounting.learning.manage")
if denied:
return denied
client, _, scope = _find_visible_client(db, request, user, client_id)
if not client:
return _go(error="Client is not visible.")
review_sales(
db,
tenant_id=scope.tenant_id,
client_id=client.id,
row_id=row_id,
final_nature_id=final_nature_id,
final_ledger_name=final_ledger_name,
review_note=review_note,
user_id=user.id,
)
return _go(
client.id,
message="Sales / income accounting treatment reviewed.",
status=status,
customer=customer,
period=period,
page=page,
per_page=per_page,
)
except Exception as exc:
db.rollback()
return _go(
client_id,
error=str(exc),
status=status,
customer=customer,
period=period,
page=page,
per_page=per_page,
)
finally:
db.close()