feat(org): panel-based access system replacing roles (procurement/inventory)
- new tables user_panels (panel: procurement/inventory/pm + level full/limited)
and user_project_members (M:N membership, separate from pm responsibility)
- UserContext carries panels + member_project_ids; /users/me and the users
list include them
- admin endpoints: PUT /users/{id}/panels, PUT /users/{id}/panel-projects
- inventory scope: panel inventory 'full' -> manager scope (all projects, NO
stock change); 'limited' -> supervisor scope (own memberships, stock change
allowed); legacy roles kept as migration fallback; admin untouched
- procurement writes: _procurement_writer is now panel-aware (admin, legacy
procurement_manager, or procurement 'full') across all 18 write routers;
quote CREATE additionally allows 'limited' users restricted to their member
projects (validated server-side)
- frontend: panel-aware HomeRoute (inventory -> warehouse panel, procurement
limited -> minimal RequesterHome), wizard project options restricted for
limited users, change-inventory button/route gated to inventory 'limited',
organization panel gains the panels & access-levels management section
This commit is contained in:
@@ -8,7 +8,7 @@ from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy import exists, func, or_, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.auth import require_role
|
||||
from app.auth import UserContext, allow_quote_create, require_procurement_writer, require_role
|
||||
from app.db.engine import get_db
|
||||
from app.models.catalog import Material
|
||||
from app.models.procurement import (
|
||||
@@ -37,14 +37,14 @@ from app.services.expand_service import (
|
||||
serialize_quote_with_children,
|
||||
serialize_quotes,
|
||||
)
|
||||
from app.utils.errors import NotFoundError
|
||||
from app.utils.errors import ForbiddenError, NotFoundError, ValidationError
|
||||
from app.utils.pagination import sort_query
|
||||
from app.utils.security import get_actor
|
||||
|
||||
router = APIRouter(prefix="/quotes", tags=["quotes"])
|
||||
|
||||
# عملیات نوشتنی تدارکات — فقط ادمین و مدیر تدارکات (مدیر پروژه فقط خواندن دارد)
|
||||
_procurement_writer = require_role("admin", "procurement_manager")
|
||||
_procurement_writer = require_procurement_writer()
|
||||
|
||||
|
||||
# --- list filter helpers ---------------------------------------------------
|
||||
@@ -164,8 +164,17 @@ async def create_quote(
|
||||
payload: QuoteCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
actor: str = Depends(get_actor),
|
||||
_auth=Depends(_procurement_writer),
|
||||
ctx: UserContext = Depends(allow_quote_create()),
|
||||
_auth=None,
|
||||
):
|
||||
# سطح limited: استعلام فقط برای پروژههای عضویت خودش
|
||||
if (ctx.panels or {}).get("procurement") == "limited" and ctx.role != "admin":
|
||||
member = set(ctx.member_project_ids or [])
|
||||
wanted = {pid for pid in ([payload.project_id] + list(payload.project_ids or [])) if pid}
|
||||
if not wanted:
|
||||
raise ValidationError("برای افزودن استعلام، پروژه الزامی است")
|
||||
if not wanted <= member:
|
||||
raise ForbiddenError("استعلام فقط برای پروژههای تخصیصیافته به شما مجاز است")
|
||||
quote = await quote_service.create_quote(db, payload, actor)
|
||||
return {"data": await serialize_quote_with_children(db, quote)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user