Spaces:
Runtime error
Runtime error
vanitha commited on
Commit ·
b707bc9
1
Parent(s): d4c0c14
dashboard layout and widget
Browse files
app/dashboard/constants.py
CHANGED
|
@@ -30,7 +30,7 @@ class DashboardStatus(str, Enum):
|
|
| 30 |
|
| 31 |
# MongoDB Collections
|
| 32 |
DASHBOARD_USER_LAYOUTS_COLLECTION = "dashboard_user_layouts"
|
| 33 |
-
WIDGET_CATALOG_COLLECTION = "
|
| 34 |
|
| 35 |
# Chart Widget IDs and Handlers
|
| 36 |
CHART_WIDGET_IDS = {
|
|
|
|
| 30 |
|
| 31 |
# MongoDB Collections
|
| 32 |
DASHBOARD_USER_LAYOUTS_COLLECTION = "dashboard_user_layouts"
|
| 33 |
+
WIDGET_CATALOG_COLLECTION = "dashboard_widgets"
|
| 34 |
|
| 35 |
# Chart Widget IDs and Handlers
|
| 36 |
CHART_WIDGET_IDS = {
|
app/dashboard/controllers/dashboard_router.py
CHANGED
|
@@ -26,14 +26,14 @@ async def get_dashboard_layout(
|
|
| 26 |
_: TokenUser = Depends(require_scm_permission("dashboard", "view"))
|
| 27 |
):
|
| 28 |
"""
|
| 29 |
-
Get dashboard layout for merchant+role
|
| 30 |
Returns layout or empty with status true if no layout exists.
|
| 31 |
"""
|
| 32 |
try:
|
| 33 |
layout_doc = await DashboardService.get_layout(
|
| 34 |
merchant_id=current_user.merchant_id,
|
| 35 |
role_id=current_user.role,
|
| 36 |
-
|
| 37 |
)
|
| 38 |
|
| 39 |
if layout_doc:
|
|
@@ -80,7 +80,7 @@ async def update_dashboard_layout(
|
|
| 80 |
merchant_id=current_user.merchant_id,
|
| 81 |
role_id=current_user.role,
|
| 82 |
layout_data=layout_data,
|
| 83 |
-
|
| 84 |
updated_by=current_user.user_id
|
| 85 |
)
|
| 86 |
|
|
@@ -121,7 +121,7 @@ async def update_widget_positions(
|
|
| 121 |
merchant_id=current_user.merchant_id,
|
| 122 |
role_id=current_user.role,
|
| 123 |
position_updates=positions_data.items,
|
| 124 |
-
|
| 125 |
updated_by=current_user.user_id
|
| 126 |
)
|
| 127 |
|
|
@@ -168,7 +168,7 @@ async def create_dashboard_widget(
|
|
| 168 |
merchant_id=current_user.merchant_id,
|
| 169 |
role_id=current_user.role,
|
| 170 |
widget_request=widget_data,
|
| 171 |
-
|
| 172 |
updated_by=current_user.user_id
|
| 173 |
)
|
| 174 |
|
|
@@ -213,7 +213,7 @@ async def delete_dashboard_widget(
|
|
| 213 |
merchant_id=current_user.merchant_id,
|
| 214 |
role_id=current_user.role,
|
| 215 |
widget_id=widget_id,
|
| 216 |
-
|
| 217 |
updated_by=current_user.user_id
|
| 218 |
)
|
| 219 |
|
|
@@ -257,7 +257,7 @@ async def reset_dashboard_layout(
|
|
| 257 |
deleted = await DashboardService.reset_layout(
|
| 258 |
merchant_id=current_user.merchant_id,
|
| 259 |
role_id=current_user.role,
|
| 260 |
-
|
| 261 |
)
|
| 262 |
|
| 263 |
logger.info(f"Dashboard layout reset", extra={
|
|
@@ -333,4 +333,6 @@ async def list_dashboard_layouts(
|
|
| 333 |
raise HTTPException(
|
| 334 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 335 |
detail="Error retrieving dashboard layouts"
|
| 336 |
-
)
|
|
|
|
|
|
|
|
|
| 26 |
_: TokenUser = Depends(require_scm_permission("dashboard", "view"))
|
| 27 |
):
|
| 28 |
"""
|
| 29 |
+
Get dashboard layout for merchant+role combination.
|
| 30 |
Returns layout or empty with status true if no layout exists.
|
| 31 |
"""
|
| 32 |
try:
|
| 33 |
layout_doc = await DashboardService.get_layout(
|
| 34 |
merchant_id=current_user.merchant_id,
|
| 35 |
role_id=current_user.role,
|
| 36 |
+
user_id=getattr(current_user, 'user_id', None)
|
| 37 |
)
|
| 38 |
|
| 39 |
if layout_doc:
|
|
|
|
| 80 |
merchant_id=current_user.merchant_id,
|
| 81 |
role_id=current_user.role,
|
| 82 |
layout_data=layout_data,
|
| 83 |
+
user_id=getattr(current_user, 'user_id', None),
|
| 84 |
updated_by=current_user.user_id
|
| 85 |
)
|
| 86 |
|
|
|
|
| 121 |
merchant_id=current_user.merchant_id,
|
| 122 |
role_id=current_user.role,
|
| 123 |
position_updates=positions_data.items,
|
| 124 |
+
user_id=getattr(current_user, 'user_id', None),
|
| 125 |
updated_by=current_user.user_id
|
| 126 |
)
|
| 127 |
|
|
|
|
| 168 |
merchant_id=current_user.merchant_id,
|
| 169 |
role_id=current_user.role,
|
| 170 |
widget_request=widget_data,
|
| 171 |
+
user_id=getattr(current_user, 'user_id', None),
|
| 172 |
updated_by=current_user.user_id
|
| 173 |
)
|
| 174 |
|
|
|
|
| 213 |
merchant_id=current_user.merchant_id,
|
| 214 |
role_id=current_user.role,
|
| 215 |
widget_id=widget_id,
|
| 216 |
+
user_id=getattr(current_user, 'user_id', None),
|
| 217 |
updated_by=current_user.user_id
|
| 218 |
)
|
| 219 |
|
|
|
|
| 257 |
deleted = await DashboardService.reset_layout(
|
| 258 |
merchant_id=current_user.merchant_id,
|
| 259 |
role_id=current_user.role,
|
| 260 |
+
user_id=getattr(current_user, 'user_id', None)
|
| 261 |
)
|
| 262 |
|
| 263 |
logger.info(f"Dashboard layout reset", extra={
|
|
|
|
| 333 |
raise HTTPException(
|
| 334 |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 335 |
detail="Error retrieving dashboard layouts"
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
|
app/dashboard/controllers/widget_router.py
CHANGED
|
@@ -9,14 +9,14 @@ from app.core.schemas import StatusResponse
|
|
| 9 |
from app.dashboard.schemas.widget_schemas import (
|
| 10 |
ChartWidgetRequest, ChartWidgetResponse, ChartListRequest,
|
| 11 |
TableWidgetRequest, TableWidgetResponse, TableListRequest,
|
| 12 |
-
KPIStatsRequest, KPIStatsResponse, IndividualKPIRequest,
|
| 13 |
IndividualKPIResponse, KPIListRequest
|
| 14 |
)
|
| 15 |
from app.dependencies.auth import get_current_user, TokenUser
|
| 16 |
from app.dependencies.scm_permissions import require_scm_permission
|
| 17 |
from app.dashboard.services.widget_data_service import WidgetDataService
|
| 18 |
from app.dashboard.constants import (
|
| 19 |
-
CHART_WIDGET_IDS, TABLE_WIDGET_IDS, KPI_WIDGET_IDS,
|
| 20 |
is_valid_widget_id, WIDGET_CATALOG_COLLECTION
|
| 21 |
)
|
| 22 |
from app.nosql import get_database
|
|
@@ -30,11 +30,9 @@ async def require_widget_access(widget_id: str, current_user: TokenUser) -> Toke
|
|
| 30 |
"""Check if user has access to specific widget via access_roles.widget_access."""
|
| 31 |
try:
|
| 32 |
db = get_database()
|
| 33 |
-
access_roles_collection = db["
|
| 34 |
-
|
| 35 |
# Find user's role permissions
|
| 36 |
role_doc = await access_roles_collection.find_one({
|
| 37 |
-
"merchant_id": current_user.merchant_id,
|
| 38 |
"role_id": current_user.role
|
| 39 |
})
|
| 40 |
|
|
@@ -302,7 +300,6 @@ async def list_chart_widgets(
|
|
| 302 |
# Build query filters
|
| 303 |
query = {
|
| 304 |
"type": "chart",
|
| 305 |
-
"status": "active"
|
| 306 |
}
|
| 307 |
|
| 308 |
# Add request filters
|
|
@@ -310,8 +307,8 @@ async def list_chart_widgets(
|
|
| 310 |
query.update(request.filters)
|
| 311 |
|
| 312 |
if request.widget_types:
|
| 313 |
-
query["
|
| 314 |
-
|
| 315 |
if request.categories:
|
| 316 |
query["category"] = {"$in": request.categories}
|
| 317 |
|
|
@@ -374,8 +371,7 @@ async def list_table_widgets(
|
|
| 374 |
|
| 375 |
# Build query filters
|
| 376 |
query = {
|
| 377 |
-
"type": "table"
|
| 378 |
-
"status": "active"
|
| 379 |
}
|
| 380 |
|
| 381 |
# Add request filters
|
|
@@ -383,8 +379,8 @@ async def list_table_widgets(
|
|
| 383 |
query.update(request.filters)
|
| 384 |
|
| 385 |
if request.widget_types:
|
| 386 |
-
query["
|
| 387 |
-
|
| 388 |
if request.categories:
|
| 389 |
query["category"] = {"$in": request.categories}
|
| 390 |
|
|
@@ -447,8 +443,7 @@ async def list_kpi_widgets(
|
|
| 447 |
|
| 448 |
# Build query filters
|
| 449 |
query = {
|
| 450 |
-
"type": "kpi"
|
| 451 |
-
"status": "active"
|
| 452 |
}
|
| 453 |
|
| 454 |
# Add request filters
|
|
|
|
| 9 |
from app.dashboard.schemas.widget_schemas import (
|
| 10 |
ChartWidgetRequest, ChartWidgetResponse, ChartListRequest,
|
| 11 |
TableWidgetRequest, TableWidgetResponse, TableListRequest,
|
| 12 |
+
KPIStatsRequest, KPIStatsResponse, IndividualKPIRequest,
|
| 13 |
IndividualKPIResponse, KPIListRequest
|
| 14 |
)
|
| 15 |
from app.dependencies.auth import get_current_user, TokenUser
|
| 16 |
from app.dependencies.scm_permissions import require_scm_permission
|
| 17 |
from app.dashboard.services.widget_data_service import WidgetDataService
|
| 18 |
from app.dashboard.constants import (
|
| 19 |
+
CHART_WIDGET_IDS, TABLE_WIDGET_IDS, KPI_WIDGET_IDS,
|
| 20 |
is_valid_widget_id, WIDGET_CATALOG_COLLECTION
|
| 21 |
)
|
| 22 |
from app.nosql import get_database
|
|
|
|
| 30 |
"""Check if user has access to specific widget via access_roles.widget_access."""
|
| 31 |
try:
|
| 32 |
db = get_database()
|
| 33 |
+
access_roles_collection = db["scm_access_roles"]
|
|
|
|
| 34 |
# Find user's role permissions
|
| 35 |
role_doc = await access_roles_collection.find_one({
|
|
|
|
| 36 |
"role_id": current_user.role
|
| 37 |
})
|
| 38 |
|
|
|
|
| 300 |
# Build query filters
|
| 301 |
query = {
|
| 302 |
"type": "chart",
|
|
|
|
| 303 |
}
|
| 304 |
|
| 305 |
# Add request filters
|
|
|
|
| 307 |
query.update(request.filters)
|
| 308 |
|
| 309 |
if request.widget_types:
|
| 310 |
+
query["type"] = {"$in": request.widget_types}
|
| 311 |
+
|
| 312 |
if request.categories:
|
| 313 |
query["category"] = {"$in": request.categories}
|
| 314 |
|
|
|
|
| 371 |
|
| 372 |
# Build query filters
|
| 373 |
query = {
|
| 374 |
+
"type": "table"
|
|
|
|
| 375 |
}
|
| 376 |
|
| 377 |
# Add request filters
|
|
|
|
| 379 |
query.update(request.filters)
|
| 380 |
|
| 381 |
if request.widget_types:
|
| 382 |
+
query["type"] = {"$in": request.widget_types}
|
| 383 |
+
|
| 384 |
if request.categories:
|
| 385 |
query["category"] = {"$in": request.categories}
|
| 386 |
|
|
|
|
| 443 |
|
| 444 |
# Build query filters
|
| 445 |
query = {
|
| 446 |
+
"type": "kpi"
|
|
|
|
| 447 |
}
|
| 448 |
|
| 449 |
# Add request filters
|
app/dashboard/services/dashboard_service.py
CHANGED
|
@@ -27,7 +27,7 @@ class DashboardService:
|
|
| 27 |
async def get_layout(
|
| 28 |
merchant_id: str,
|
| 29 |
role_id: str,
|
| 30 |
-
|
| 31 |
) -> Optional[Dict[str, Any]]:
|
| 32 |
"""
|
| 33 |
Get dashboard layout for merchant+role+associate combination.
|
|
@@ -43,11 +43,11 @@ class DashboardService:
|
|
| 43 |
"role_id": role_id
|
| 44 |
}
|
| 45 |
|
| 46 |
-
# Add
|
| 47 |
-
if
|
| 48 |
-
query["
|
| 49 |
else:
|
| 50 |
-
query["
|
| 51 |
|
| 52 |
layout_doc = await collection.find_one(query)
|
| 53 |
|
|
@@ -55,14 +55,14 @@ class DashboardService:
|
|
| 55 |
logger.info(f"Layout found", extra={
|
| 56 |
"merchant_id": merchant_id,
|
| 57 |
"role_id": role_id,
|
| 58 |
-
"
|
| 59 |
"widget_count": len(layout_doc.get("widgets", []))
|
| 60 |
})
|
| 61 |
else:
|
| 62 |
logger.info(f"No layout found", extra={
|
| 63 |
"merchant_id": merchant_id,
|
| 64 |
"role_id": role_id,
|
| 65 |
-
"
|
| 66 |
})
|
| 67 |
|
| 68 |
return layout_doc
|
|
@@ -76,7 +76,7 @@ class DashboardService:
|
|
| 76 |
merchant_id: str,
|
| 77 |
role_id: str,
|
| 78 |
layout_data: DashboardLayoutCreate,
|
| 79 |
-
|
| 80 |
updated_by: Optional[str] = None
|
| 81 |
) -> Dict[str, Any]:
|
| 82 |
"""
|
|
@@ -93,8 +93,8 @@ class DashboardService:
|
|
| 93 |
"role_id": role_id
|
| 94 |
}
|
| 95 |
|
| 96 |
-
if
|
| 97 |
-
query["
|
| 98 |
|
| 99 |
# Hydrate widget data_config from catalog
|
| 100 |
widgets_with_config = []
|
|
@@ -141,7 +141,7 @@ class DashboardService:
|
|
| 141 |
logger.info(f"Layout {'created' if result.upserted_id else 'updated'}", extra={
|
| 142 |
"merchant_id": merchant_id,
|
| 143 |
"role_id": role_id,
|
| 144 |
-
"
|
| 145 |
"widget_count": len(widgets_with_config),
|
| 146 |
"upserted": bool(result.upserted_id)
|
| 147 |
})
|
|
@@ -157,7 +157,7 @@ class DashboardService:
|
|
| 157 |
merchant_id: str,
|
| 158 |
role_id: str,
|
| 159 |
position_updates: List[WidgetPositionUpdate],
|
| 160 |
-
|
| 161 |
updated_by: Optional[str] = None
|
| 162 |
) -> Dict[str, Any]:
|
| 163 |
"""
|
|
@@ -174,8 +174,8 @@ class DashboardService:
|
|
| 174 |
"role_id": role_id
|
| 175 |
}
|
| 176 |
|
| 177 |
-
if
|
| 178 |
-
query["
|
| 179 |
|
| 180 |
# Get current layout
|
| 181 |
current_layout = await collection.find_one(query)
|
|
@@ -214,7 +214,7 @@ class DashboardService:
|
|
| 214 |
logger.info(f"Widget positions updated", extra={
|
| 215 |
"merchant_id": merchant_id,
|
| 216 |
"role_id": role_id,
|
| 217 |
-
"
|
| 218 |
"updates_count": len(position_updates)
|
| 219 |
})
|
| 220 |
|
|
@@ -229,7 +229,7 @@ class DashboardService:
|
|
| 229 |
merchant_id: str,
|
| 230 |
role_id: str,
|
| 231 |
widget_request: CreateWidgetRequest,
|
| 232 |
-
|
| 233 |
updated_by: Optional[str] = None
|
| 234 |
) -> Dict[str, Any]:
|
| 235 |
"""
|
|
@@ -246,8 +246,8 @@ class DashboardService:
|
|
| 246 |
"role_id": role_id
|
| 247 |
}
|
| 248 |
|
| 249 |
-
if
|
| 250 |
-
query["
|
| 251 |
|
| 252 |
# Get current layout or create new one
|
| 253 |
current_layout = await collection.find_one(query)
|
|
@@ -258,7 +258,7 @@ class DashboardService:
|
|
| 258 |
grid_settings=None # Will use defaults
|
| 259 |
)
|
| 260 |
return await DashboardService.create_or_update_layout(
|
| 261 |
-
merchant_id, role_id, layout_data,
|
| 262 |
)
|
| 263 |
|
| 264 |
# Add widget to existing layout
|
|
@@ -293,7 +293,7 @@ class DashboardService:
|
|
| 293 |
logger.info(f"Widget added to layout", extra={
|
| 294 |
"merchant_id": merchant_id,
|
| 295 |
"role_id": role_id,
|
| 296 |
-
"
|
| 297 |
"widget_id": widget_request.widget_id,
|
| 298 |
"widget_type": widget_request.type
|
| 299 |
})
|
|
@@ -309,7 +309,7 @@ class DashboardService:
|
|
| 309 |
merchant_id: str,
|
| 310 |
role_id: str,
|
| 311 |
widget_id: str,
|
| 312 |
-
|
| 313 |
updated_by: Optional[str] = None
|
| 314 |
) -> Dict[str, Any]:
|
| 315 |
"""
|
|
@@ -326,8 +326,8 @@ class DashboardService:
|
|
| 326 |
"role_id": role_id
|
| 327 |
}
|
| 328 |
|
| 329 |
-
if
|
| 330 |
-
query["
|
| 331 |
|
| 332 |
# Get current layout
|
| 333 |
current_layout = await collection.find_one(query)
|
|
@@ -355,7 +355,7 @@ class DashboardService:
|
|
| 355 |
logger.info(f"Widget removed from layout", extra={
|
| 356 |
"merchant_id": merchant_id,
|
| 357 |
"role_id": role_id,
|
| 358 |
-
"
|
| 359 |
"widget_id": widget_id
|
| 360 |
})
|
| 361 |
|
|
@@ -369,7 +369,7 @@ class DashboardService:
|
|
| 369 |
async def reset_layout(
|
| 370 |
merchant_id: str,
|
| 371 |
role_id: str,
|
| 372 |
-
|
| 373 |
) -> bool:
|
| 374 |
"""
|
| 375 |
Delete layout for merchant+role+associate combination.
|
|
@@ -385,17 +385,17 @@ class DashboardService:
|
|
| 385 |
"role_id": role_id
|
| 386 |
}
|
| 387 |
|
| 388 |
-
if
|
| 389 |
-
query["
|
| 390 |
else:
|
| 391 |
-
query["
|
| 392 |
|
| 393 |
result = await collection.delete_one(query)
|
| 394 |
|
| 395 |
logger.info(f"Layout reset", extra={
|
| 396 |
"merchant_id": merchant_id,
|
| 397 |
"role_id": role_id,
|
| 398 |
-
"
|
| 399 |
"deleted": result.deleted_count > 0
|
| 400 |
})
|
| 401 |
|
|
|
|
| 27 |
async def get_layout(
|
| 28 |
merchant_id: str,
|
| 29 |
role_id: str,
|
| 30 |
+
user_id: Optional[str] = None
|
| 31 |
) -> Optional[Dict[str, Any]]:
|
| 32 |
"""
|
| 33 |
Get dashboard layout for merchant+role+associate combination.
|
|
|
|
| 43 |
"role_id": role_id
|
| 44 |
}
|
| 45 |
|
| 46 |
+
# Add user_id if provided (for personal layouts)
|
| 47 |
+
if user_id:
|
| 48 |
+
query["user_id"] = user_id
|
| 49 |
else:
|
| 50 |
+
query["user_id"] = {"$exists": False}
|
| 51 |
|
| 52 |
layout_doc = await collection.find_one(query)
|
| 53 |
|
|
|
|
| 55 |
logger.info(f"Layout found", extra={
|
| 56 |
"merchant_id": merchant_id,
|
| 57 |
"role_id": role_id,
|
| 58 |
+
"user_id": user_id,
|
| 59 |
"widget_count": len(layout_doc.get("widgets", []))
|
| 60 |
})
|
| 61 |
else:
|
| 62 |
logger.info(f"No layout found", extra={
|
| 63 |
"merchant_id": merchant_id,
|
| 64 |
"role_id": role_id,
|
| 65 |
+
"user_id": user_id
|
| 66 |
})
|
| 67 |
|
| 68 |
return layout_doc
|
|
|
|
| 76 |
merchant_id: str,
|
| 77 |
role_id: str,
|
| 78 |
layout_data: DashboardLayoutCreate,
|
| 79 |
+
user_id: Optional[str] = None,
|
| 80 |
updated_by: Optional[str] = None
|
| 81 |
) -> Dict[str, Any]:
|
| 82 |
"""
|
|
|
|
| 93 |
"role_id": role_id
|
| 94 |
}
|
| 95 |
|
| 96 |
+
if user_id:
|
| 97 |
+
query["user_id"] = user_id
|
| 98 |
|
| 99 |
# Hydrate widget data_config from catalog
|
| 100 |
widgets_with_config = []
|
|
|
|
| 141 |
logger.info(f"Layout {'created' if result.upserted_id else 'updated'}", extra={
|
| 142 |
"merchant_id": merchant_id,
|
| 143 |
"role_id": role_id,
|
| 144 |
+
"user_id": user_id,
|
| 145 |
"widget_count": len(widgets_with_config),
|
| 146 |
"upserted": bool(result.upserted_id)
|
| 147 |
})
|
|
|
|
| 157 |
merchant_id: str,
|
| 158 |
role_id: str,
|
| 159 |
position_updates: List[WidgetPositionUpdate],
|
| 160 |
+
user_id: Optional[str] = None,
|
| 161 |
updated_by: Optional[str] = None
|
| 162 |
) -> Dict[str, Any]:
|
| 163 |
"""
|
|
|
|
| 174 |
"role_id": role_id
|
| 175 |
}
|
| 176 |
|
| 177 |
+
if user_id:
|
| 178 |
+
query["user_id"] = user_id
|
| 179 |
|
| 180 |
# Get current layout
|
| 181 |
current_layout = await collection.find_one(query)
|
|
|
|
| 214 |
logger.info(f"Widget positions updated", extra={
|
| 215 |
"merchant_id": merchant_id,
|
| 216 |
"role_id": role_id,
|
| 217 |
+
"user_id": user_id,
|
| 218 |
"updates_count": len(position_updates)
|
| 219 |
})
|
| 220 |
|
|
|
|
| 229 |
merchant_id: str,
|
| 230 |
role_id: str,
|
| 231 |
widget_request: CreateWidgetRequest,
|
| 232 |
+
user_id: Optional[str] = None,
|
| 233 |
updated_by: Optional[str] = None
|
| 234 |
) -> Dict[str, Any]:
|
| 235 |
"""
|
|
|
|
| 246 |
"role_id": role_id
|
| 247 |
}
|
| 248 |
|
| 249 |
+
if user_id:
|
| 250 |
+
query["user_id"] = user_id
|
| 251 |
|
| 252 |
# Get current layout or create new one
|
| 253 |
current_layout = await collection.find_one(query)
|
|
|
|
| 258 |
grid_settings=None # Will use defaults
|
| 259 |
)
|
| 260 |
return await DashboardService.create_or_update_layout(
|
| 261 |
+
merchant_id, role_id, layout_data, user_id, updated_by
|
| 262 |
)
|
| 263 |
|
| 264 |
# Add widget to existing layout
|
|
|
|
| 293 |
logger.info(f"Widget added to layout", extra={
|
| 294 |
"merchant_id": merchant_id,
|
| 295 |
"role_id": role_id,
|
| 296 |
+
"user_id": user_id,
|
| 297 |
"widget_id": widget_request.widget_id,
|
| 298 |
"widget_type": widget_request.type
|
| 299 |
})
|
|
|
|
| 309 |
merchant_id: str,
|
| 310 |
role_id: str,
|
| 311 |
widget_id: str,
|
| 312 |
+
user_id: Optional[str] = None,
|
| 313 |
updated_by: Optional[str] = None
|
| 314 |
) -> Dict[str, Any]:
|
| 315 |
"""
|
|
|
|
| 326 |
"role_id": role_id
|
| 327 |
}
|
| 328 |
|
| 329 |
+
if user_id:
|
| 330 |
+
query["user_id"] = user_id
|
| 331 |
|
| 332 |
# Get current layout
|
| 333 |
current_layout = await collection.find_one(query)
|
|
|
|
| 355 |
logger.info(f"Widget removed from layout", extra={
|
| 356 |
"merchant_id": merchant_id,
|
| 357 |
"role_id": role_id,
|
| 358 |
+
"user_id": user_id,
|
| 359 |
"widget_id": widget_id
|
| 360 |
})
|
| 361 |
|
|
|
|
| 369 |
async def reset_layout(
|
| 370 |
merchant_id: str,
|
| 371 |
role_id: str,
|
| 372 |
+
user_id: Optional[str] = None
|
| 373 |
) -> bool:
|
| 374 |
"""
|
| 375 |
Delete layout for merchant+role+associate combination.
|
|
|
|
| 385 |
"role_id": role_id
|
| 386 |
}
|
| 387 |
|
| 388 |
+
if user_id:
|
| 389 |
+
query["user_id"] = user_id
|
| 390 |
else:
|
| 391 |
+
query["user_id"] = {"$exists": False}
|
| 392 |
|
| 393 |
result = await collection.delete_one(query)
|
| 394 |
|
| 395 |
logger.info(f"Layout reset", extra={
|
| 396 |
"merchant_id": merchant_id,
|
| 397 |
"role_id": role_id,
|
| 398 |
+
"user_id": user_id,
|
| 399 |
"deleted": result.deleted_count > 0
|
| 400 |
})
|
| 401 |
|
app/dashboard/services/widget_data_service.py
CHANGED
|
@@ -4,6 +4,7 @@ Handles data aggregation and retrieval for dashboard widgets.
|
|
| 4 |
"""
|
| 5 |
from datetime import datetime, timedelta
|
| 6 |
from typing import Optional, List, Dict, Any
|
|
|
|
| 7 |
from sqlalchemy import text
|
| 8 |
from app.core.logging import get_logger
|
| 9 |
from app.sql import async_session
|
|
@@ -41,9 +42,10 @@ class WidgetDataService:
|
|
| 41 |
if use_cache:
|
| 42 |
cached_data = await cache_service.get(cache_key)
|
| 43 |
if cached_data:
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
|
|
|
| 47 |
# Get widget configuration
|
| 48 |
widget_config = get_widget_config(widget_id)
|
| 49 |
if not widget_config:
|
|
@@ -64,8 +66,8 @@ class WidgetDataService:
|
|
| 64 |
# Cache the result
|
| 65 |
if use_cache:
|
| 66 |
ttl = get_cache_ttl_for_widget(widget_id)
|
| 67 |
-
await cache_service.set(cache_key, data, ttl)
|
| 68 |
-
|
| 69 |
return data
|
| 70 |
|
| 71 |
except Exception as e:
|
|
@@ -94,9 +96,10 @@ class WidgetDataService:
|
|
| 94 |
if use_cache:
|
| 95 |
cached_data = await cache_service.get(cache_key)
|
| 96 |
if cached_data:
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
|
|
|
| 100 |
# Get widget configuration
|
| 101 |
widget_config = get_widget_config(widget_id)
|
| 102 |
if not widget_config:
|
|
@@ -117,8 +120,8 @@ class WidgetDataService:
|
|
| 117 |
# Cache the result
|
| 118 |
if use_cache:
|
| 119 |
ttl = get_cache_ttl_for_widget(widget_id)
|
| 120 |
-
await cache_service.set(cache_key, data, ttl)
|
| 121 |
-
|
| 122 |
return data
|
| 123 |
|
| 124 |
except Exception as e:
|
|
@@ -143,8 +146,8 @@ class WidgetDataService:
|
|
| 143 |
if use_cache:
|
| 144 |
cached_data = await cache_service.get(cache_key)
|
| 145 |
if cached_data:
|
| 146 |
-
return cached_data
|
| 147 |
-
|
| 148 |
# Get KPI data (mock for now)
|
| 149 |
kpis = {}
|
| 150 |
charts = {} if include_charts else None
|
|
@@ -199,8 +202,8 @@ class WidgetDataService:
|
|
| 199 |
# Cache the result
|
| 200 |
if use_cache:
|
| 201 |
ttl = get_cache_ttl_for_widget("kpi_stats")
|
| 202 |
-
await cache_service.set(cache_key, data, ttl)
|
| 203 |
-
|
| 204 |
return data
|
| 205 |
|
| 206 |
except Exception as e:
|
|
|
|
| 4 |
"""
|
| 5 |
from datetime import datetime, timedelta
|
| 6 |
from typing import Optional, List, Dict, Any
|
| 7 |
+
import json
|
| 8 |
from sqlalchemy import text
|
| 9 |
from app.core.logging import get_logger
|
| 10 |
from app.sql import async_session
|
|
|
|
| 42 |
if use_cache:
|
| 43 |
cached_data = await cache_service.get(cache_key)
|
| 44 |
if cached_data:
|
| 45 |
+
data = json.loads(cached_data)
|
| 46 |
+
data["cached"] = True
|
| 47 |
+
return data
|
| 48 |
+
|
| 49 |
# Get widget configuration
|
| 50 |
widget_config = get_widget_config(widget_id)
|
| 51 |
if not widget_config:
|
|
|
|
| 66 |
# Cache the result
|
| 67 |
if use_cache:
|
| 68 |
ttl = get_cache_ttl_for_widget(widget_id)
|
| 69 |
+
await cache_service.set(cache_key, json.dumps(data), ttl)
|
| 70 |
+
|
| 71 |
return data
|
| 72 |
|
| 73 |
except Exception as e:
|
|
|
|
| 96 |
if use_cache:
|
| 97 |
cached_data = await cache_service.get(cache_key)
|
| 98 |
if cached_data:
|
| 99 |
+
data = json.loads(cached_data)
|
| 100 |
+
data["cached"] = True
|
| 101 |
+
return data
|
| 102 |
+
|
| 103 |
# Get widget configuration
|
| 104 |
widget_config = get_widget_config(widget_id)
|
| 105 |
if not widget_config:
|
|
|
|
| 120 |
# Cache the result
|
| 121 |
if use_cache:
|
| 122 |
ttl = get_cache_ttl_for_widget(widget_id)
|
| 123 |
+
await cache_service.set(cache_key, json.dumps(data), ttl)
|
| 124 |
+
|
| 125 |
return data
|
| 126 |
|
| 127 |
except Exception as e:
|
|
|
|
| 146 |
if use_cache:
|
| 147 |
cached_data = await cache_service.get(cache_key)
|
| 148 |
if cached_data:
|
| 149 |
+
return json.loads(cached_data)
|
| 150 |
+
|
| 151 |
# Get KPI data (mock for now)
|
| 152 |
kpis = {}
|
| 153 |
charts = {} if include_charts else None
|
|
|
|
| 202 |
# Cache the result
|
| 203 |
if use_cache:
|
| 204 |
ttl = get_cache_ttl_for_widget("kpi_stats")
|
| 205 |
+
await cache_service.set(cache_key, json.dumps(data), ttl)
|
| 206 |
+
|
| 207 |
return data
|
| 208 |
|
| 209 |
except Exception as e:
|
app/main.py
CHANGED
|
@@ -8,6 +8,7 @@ from fastapi.responses import JSONResponse
|
|
| 8 |
from fastapi.exceptions import RequestValidationError
|
| 9 |
from jose import JWTError
|
| 10 |
from pymongo.errors import PyMongoError, ConnectionFailure, OperationFailure
|
|
|
|
| 11 |
from app.core.logging import get_logger, setup_logging
|
| 12 |
from app.core.config import settings
|
| 13 |
|
|
@@ -86,7 +87,7 @@ async def startup_event():
|
|
| 86 |
logger.info("Starting SCM Microservice")
|
| 87 |
await connect_to_mongo()
|
| 88 |
await connect_to_database()
|
| 89 |
-
|
| 90 |
# Start sync workers
|
| 91 |
try:
|
| 92 |
from app.superadmin.sync_service import get_sync_management_service
|
|
@@ -115,6 +116,7 @@ async def shutdown_event():
|
|
| 115 |
|
| 116 |
await close_mongo_connection()
|
| 117 |
await disconnect_from_database()
|
|
|
|
| 118 |
logger.info("SCM Microservice shut down successfully")
|
| 119 |
|
| 120 |
|
|
|
|
| 8 |
from fastapi.exceptions import RequestValidationError
|
| 9 |
from jose import JWTError
|
| 10 |
from pymongo.errors import PyMongoError, ConnectionFailure, OperationFailure
|
| 11 |
+
from app.cache import close_redis_connection, connect_to_redis
|
| 12 |
from app.core.logging import get_logger, setup_logging
|
| 13 |
from app.core.config import settings
|
| 14 |
|
|
|
|
| 87 |
logger.info("Starting SCM Microservice")
|
| 88 |
await connect_to_mongo()
|
| 89 |
await connect_to_database()
|
| 90 |
+
await connect_to_redis()
|
| 91 |
# Start sync workers
|
| 92 |
try:
|
| 93 |
from app.superadmin.sync_service import get_sync_management_service
|
|
|
|
| 116 |
|
| 117 |
await close_mongo_connection()
|
| 118 |
await disconnect_from_database()
|
| 119 |
+
await close_redis_connection()
|
| 120 |
logger.info("SCM Microservice shut down successfully")
|
| 121 |
|
| 122 |
|