fix: wire Velocity-OS live CRM and inventory data
Some checks failed
Velocity-OS Deployment Pipeline / lint (push) Has been cancelled
Velocity-OS Deployment Pipeline / build-and-push (agents) (push) Has been cancelled
Velocity-OS Deployment Pipeline / build-and-push (core) (push) Has been cancelled
Velocity-OS Deployment Pipeline / build-and-push (media-engine) (push) Has been cancelled
Velocity-OS Deployment Pipeline / build-and-push (webos) (push) Has been cancelled
Velocity-OS Deployment Pipeline / sign-images (agents) (push) Has been cancelled
Velocity-OS Deployment Pipeline / sign-images (core) (push) Has been cancelled
Velocity-OS Deployment Pipeline / sign-images (media-engine) (push) Has been cancelled
Velocity-OS Deployment Pipeline / sign-images (webos) (push) Has been cancelled
Velocity-OS Deployment Pipeline / notify-ingress (push) Has been cancelled
Some checks failed
Velocity-OS Deployment Pipeline / lint (push) Has been cancelled
Velocity-OS Deployment Pipeline / build-and-push (agents) (push) Has been cancelled
Velocity-OS Deployment Pipeline / build-and-push (core) (push) Has been cancelled
Velocity-OS Deployment Pipeline / build-and-push (media-engine) (push) Has been cancelled
Velocity-OS Deployment Pipeline / build-and-push (webos) (push) Has been cancelled
Velocity-OS Deployment Pipeline / sign-images (agents) (push) Has been cancelled
Velocity-OS Deployment Pipeline / sign-images (core) (push) Has been cancelled
Velocity-OS Deployment Pipeline / sign-images (media-engine) (push) Has been cancelled
Velocity-OS Deployment Pipeline / sign-images (webos) (push) Has been cancelled
Velocity-OS Deployment Pipeline / notify-ingress (push) Has been cancelled
This commit is contained in:
@@ -1356,6 +1356,7 @@ async def get_kanban_board(
|
|||||||
|
|
||||||
|
|
||||||
@crm_router.get("/pipeline/kanban")
|
@crm_router.get("/pipeline/kanban")
|
||||||
|
@crm_router.get("/crm/pipeline/kanban")
|
||||||
async def get_pipeline_kanban(
|
async def get_pipeline_kanban(
|
||||||
request: Request,
|
request: Request,
|
||||||
user: UserPrincipal = Depends(get_current_user),
|
user: UserPrincipal = Depends(get_current_user),
|
||||||
|
|||||||
@@ -47,6 +47,91 @@ def _tenant_scope(user) -> str:
|
|||||||
return user.tenant_id
|
return user.tenant_id
|
||||||
|
|
||||||
|
|
||||||
|
def _as_float(value: Any) -> float | None:
|
||||||
|
return float(value) if value is not None else None
|
||||||
|
|
||||||
|
|
||||||
|
def _canonical_project_payload(row: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
location = row.get("location_json")
|
||||||
|
amenities = row.get("amenities_json")
|
||||||
|
unit_mix = row.get("unit_mix")
|
||||||
|
price_min = row.get("price_min")
|
||||||
|
price_max = row.get("price_max")
|
||||||
|
return {
|
||||||
|
"property_id": str(row["project_id"]),
|
||||||
|
"project_name": row["project_name"],
|
||||||
|
"developer_name": row["developer_name"],
|
||||||
|
"property_type": "project",
|
||||||
|
"location": {
|
||||||
|
"city": row.get("city"),
|
||||||
|
"district": row.get("micro_market"),
|
||||||
|
"area": row.get("micro_market"),
|
||||||
|
"address": row.get("address"),
|
||||||
|
**(location if isinstance(location, dict) else {}),
|
||||||
|
},
|
||||||
|
"price_bands": [
|
||||||
|
{
|
||||||
|
"label": "Current inventory",
|
||||||
|
"min": _as_float(price_min),
|
||||||
|
"max": _as_float(price_max),
|
||||||
|
"currency": "INR lakh",
|
||||||
|
}
|
||||||
|
] if price_min is not None or price_max is not None else [],
|
||||||
|
"unit_mix": unit_mix if isinstance(unit_mix, list) else [],
|
||||||
|
"amenities": amenities if isinstance(amenities, list) else [],
|
||||||
|
"status": row.get("project_status"),
|
||||||
|
"ingested_at": row.get("created_at"),
|
||||||
|
"created_at": row.get("created_at"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def _fetch_canonical_project_rows(conn: Any, limit: int, offset: int, project_id: str | None = None) -> list[dict[str, Any]]:
|
||||||
|
filters = ""
|
||||||
|
params: list[Any] = []
|
||||||
|
if project_id is not None:
|
||||||
|
filters = "WHERE p.project_id = $1"
|
||||||
|
params.append(project_id)
|
||||||
|
rows = await conn.fetch(
|
||||||
|
f"""
|
||||||
|
SELECT
|
||||||
|
p.project_id,
|
||||||
|
p.project_name,
|
||||||
|
p.developer_name,
|
||||||
|
p.city,
|
||||||
|
p.micro_market,
|
||||||
|
p.address,
|
||||||
|
p.project_status,
|
||||||
|
p.location_json,
|
||||||
|
p.amenities_json,
|
||||||
|
p.created_at,
|
||||||
|
MIN(u.price_current) AS price_min,
|
||||||
|
MAX(u.price_current) AS price_max,
|
||||||
|
COALESCE(
|
||||||
|
jsonb_agg(
|
||||||
|
DISTINCT jsonb_build_object(
|
||||||
|
'configuration', u.configuration,
|
||||||
|
'count', 1,
|
||||||
|
'available', CASE WHEN u.status = 'available' THEN 1 ELSE 0 END,
|
||||||
|
'area', CASE WHEN u.area_sqft IS NULL THEN NULL ELSE concat(u.area_sqft::text, ' sqft') END,
|
||||||
|
'price', CASE WHEN u.price_current IS NULL THEN NULL ELSE concat('INR ', u.price_current::text, ' lakh') END
|
||||||
|
)
|
||||||
|
) FILTER (WHERE u.unit_id IS NOT NULL),
|
||||||
|
'[]'::jsonb
|
||||||
|
) AS unit_mix
|
||||||
|
FROM inventory_projects p
|
||||||
|
LEFT JOIN inventory_units u ON u.project_id = p.project_id
|
||||||
|
{filters}
|
||||||
|
GROUP BY p.project_id
|
||||||
|
ORDER BY p.project_name ASC
|
||||||
|
LIMIT ${len(params) + 1} OFFSET ${len(params) + 2}
|
||||||
|
""",
|
||||||
|
*params,
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
)
|
||||||
|
return [_canonical_project_payload(dict(row)) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
# ── Pydantic Models ───────────────────────────────────────────────────────────
|
# ── Pydantic Models ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
VALID_SOURCE_TYPES = {"csv", "json", "api_push", "manual"}
|
VALID_SOURCE_TYPES = {"csv", "json", "api_push", "manual"}
|
||||||
@@ -237,6 +322,10 @@ async def list_properties(
|
|||||||
total = await conn.fetchval(
|
total = await conn.fetchval(
|
||||||
f"SELECT COUNT(*) FROM inventory_properties {where_clause}", *params,
|
f"SELECT COUNT(*) FROM inventory_properties {where_clause}", *params,
|
||||||
)
|
)
|
||||||
|
if total == 0 and not status_filter and not property_type:
|
||||||
|
canonical_rows = await _fetch_canonical_project_rows(conn, limit, offset)
|
||||||
|
canonical_total = await conn.fetchval("SELECT COUNT(*) FROM inventory_projects")
|
||||||
|
return {"total": canonical_total, "limit": limit, "offset": offset, "properties": canonical_rows}
|
||||||
return {"total": total, "limit": limit, "offset": offset, "properties": [dict(r) for r in rows]}
|
return {"total": total, "limit": limit, "offset": offset, "properties": [dict(r) for r in rows]}
|
||||||
|
|
||||||
|
|
||||||
@@ -252,6 +341,10 @@ async def get_property(
|
|||||||
"SELECT * FROM inventory_properties WHERE property_id=$1 AND tenant_id=$2",
|
"SELECT * FROM inventory_properties WHERE property_id=$1 AND tenant_id=$2",
|
||||||
property_id, _tenant_scope(user),
|
property_id, _tenant_scope(user),
|
||||||
)
|
)
|
||||||
|
if not row:
|
||||||
|
canonical_rows = await _fetch_canonical_project_rows(conn, limit=1, offset=0, project_id=property_id)
|
||||||
|
if canonical_rows:
|
||||||
|
return canonical_rows[0]
|
||||||
if not row:
|
if not row:
|
||||||
raise HTTPException(404, "Property not found")
|
raise HTTPException(404, "Property not found")
|
||||||
return dict(row)
|
return dict(row)
|
||||||
|
|||||||
Reference in New Issue
Block a user