feat(rbac): enforce procurement write restrictions and support supervisor role in SSO
This commit is contained in:
@@ -9,6 +9,7 @@ from fastapi import APIRouter, Depends, Query, Request
|
||||
from sqlalchemy import exists, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.auth import require_role
|
||||
from app.db.engine import get_db
|
||||
from app.models.catalog import (
|
||||
AttributeDefinition,
|
||||
@@ -38,6 +39,7 @@ from app.utils.errors import ConflictError, NotFoundError, ValidationError
|
||||
from app.utils.pagination import paginate, sort_query
|
||||
|
||||
router = APIRouter(prefix="/materials", tags=["materials"])
|
||||
_procurement_writer = require_role("admin", "procurement_manager")
|
||||
|
||||
|
||||
def _coerce_filter_value(data_type: str, raw: str):
|
||||
@@ -125,7 +127,10 @@ async def list_materials(
|
||||
|
||||
@router.post("", status_code=201)
|
||||
async def create_material(
|
||||
payload: MaterialCreate, expand: str | None = None, db: AsyncSession = Depends(get_db)
|
||||
payload: MaterialCreate,
|
||||
expand: str | None = None,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_auth=Depends(_procurement_writer),
|
||||
):
|
||||
material = await material_service.create_material(db, payload)
|
||||
return {"data": await serialize_material(db, material, parse_expand(expand))}
|
||||
@@ -140,7 +145,12 @@ async def get_material(material_id, expand: str | None = None, db: AsyncSession
|
||||
|
||||
|
||||
@router.patch("/{material_id}")
|
||||
async def update_material(material_id, payload: MaterialUpdate, db: AsyncSession = Depends(get_db)):
|
||||
async def update_material(
|
||||
material_id,
|
||||
payload: MaterialUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_auth=Depends(_procurement_writer),
|
||||
):
|
||||
material = await db.get(Material, material_id)
|
||||
if material is None:
|
||||
raise NotFoundError("متریال یافت نشد")
|
||||
@@ -149,7 +159,11 @@ async def update_material(material_id, payload: MaterialUpdate, db: AsyncSession
|
||||
|
||||
|
||||
@router.delete("/{material_id}")
|
||||
async def delete_material(material_id, db: AsyncSession = Depends(get_db)):
|
||||
async def delete_material(
|
||||
material_id,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_auth=Depends(_procurement_writer),
|
||||
):
|
||||
material = await db.get(Material, material_id)
|
||||
if material is None:
|
||||
raise NotFoundError("متریال یافت نشد")
|
||||
@@ -248,7 +262,10 @@ async def list_material_units(
|
||||
|
||||
@router.post("/{material_id}/units", status_code=201)
|
||||
async def create_material_unit(
|
||||
material_id, payload: MaterialUnitCreate, db: AsyncSession = Depends(get_db)
|
||||
material_id,
|
||||
payload: MaterialUnitCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_auth=Depends(_procurement_writer),
|
||||
):
|
||||
async with db.begin():
|
||||
await _material_or_404(db, material_id)
|
||||
@@ -287,7 +304,11 @@ async def create_material_unit(
|
||||
|
||||
@router.patch("/{material_id}/units/{mu_id}")
|
||||
async def update_material_unit(
|
||||
material_id, mu_id, payload: MaterialUnitUpdate, db: AsyncSession = Depends(get_db)
|
||||
material_id,
|
||||
mu_id,
|
||||
payload: MaterialUnitUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_auth=Depends(_procurement_writer),
|
||||
):
|
||||
async with db.begin():
|
||||
mu = await db.scalar(
|
||||
@@ -319,7 +340,12 @@ async def update_material_unit(
|
||||
|
||||
|
||||
@router.delete("/{material_id}/units/{mu_id}")
|
||||
async def delete_material_unit(material_id, mu_id, db: AsyncSession = Depends(get_db)):
|
||||
async def delete_material_unit(
|
||||
material_id,
|
||||
mu_id,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_auth=Depends(_procurement_writer),
|
||||
):
|
||||
mu = await db.scalar(
|
||||
select(MaterialUnit).where(MaterialUnit.id == mu_id, MaterialUnit.material_id == material_id)
|
||||
)
|
||||
@@ -365,7 +391,10 @@ async def get_material_attribute_values(
|
||||
|
||||
@router.put("/{material_id}/attribute-values")
|
||||
async def put_material_attribute_values(
|
||||
material_id, payload: MaterialAttributeValuesPut, db: AsyncSession = Depends(get_db)
|
||||
material_id,
|
||||
payload: MaterialAttributeValuesPut,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_auth=Depends(_procurement_writer),
|
||||
):
|
||||
"""FULL replacement of a material's EAV values (empty array deletes all)."""
|
||||
async with db.begin():
|
||||
|
||||
Reference in New Issue
Block a user