diff --git a/salesflow-saas/MASTER_OPERATING_PROMPT.md b/salesflow-saas/MASTER_OPERATING_PROMPT.md index 90b27e1f..2b93f6cc 100644 --- a/salesflow-saas/MASTER_OPERATING_PROMPT.md +++ b/salesflow-saas/MASTER_OPERATING_PROMPT.md @@ -83,7 +83,7 @@ Implementation: [`backend/app/openclaw/policy.py`](backend/app/openclaw/policy.p 3. **Trust-enforced** — No sensitive action bypasses the policy gate. 4. **Data-governed** — All data flows through governed ingestion with quality checks. 5. **Arabic-first** — All user-facing content defaults to Arabic, with English as secondary. -6. **Saudi-ready** — PDPL, ZATCA, SDAIA, NCA controls are live, not aspirational. +6. **Saudi-ready** — PDPL consent checks, ZATCA invoicing, SDAIA AI governance, and NCA cybersecurity controls are implemented with live enforcement on the golden path and Saudi workflow. Full production coverage is tracked in `docs/current-vs-target-register.md`. 7. **Board-usable** — Executive surfaces show what changed, what needs decision, what is at risk. 8. **Enterprise-saleable** — Evidence packs, audit trails, and compliance matrices are exportable. diff --git a/salesflow-saas/backend/app/services/forecast_control_center.py b/salesflow-saas/backend/app/services/forecast_control_center.py index ada14946..5115242f 100644 --- a/salesflow-saas/backend/app/services/forecast_control_center.py +++ b/salesflow-saas/backend/app/services/forecast_control_center.py @@ -95,7 +95,30 @@ class ForecastControlCenter: return {"tenant_id": tenant_id, "top_variances": variances, "root_causes": [], "recommendations": []} async def get_accuracy_trend(self, db: AsyncSession, tenant_id: str, periods: int = 6) -> Dict[str, Any]: - return {"tenant_id": tenant_id, "periods": periods, "trend": [], "average_accuracy_percent": 0.0} + """Returns forecast accuracy based on actual closed deal data.""" + from app.models.deal import Deal + tid = UUID(tenant_id) + + closed_won = float( + (await db.execute( + select(func.coalesce(func.sum(Deal.value), 0)) + .where(Deal.tenant_id == tid, Deal.stage == "closed_won") + )).scalar() or 0 + ) + total_pipeline = float( + (await db.execute( + select(func.coalesce(func.sum(Deal.value), 0)) + .where(Deal.tenant_id == tid) + )).scalar() or 0 + ) + accuracy = round((closed_won / total_pipeline * 100), 1) if total_pipeline else 0.0 + + return { + "tenant_id": tenant_id, + "periods": periods, + "trend": [{"period": "current", "accuracy_percent": accuracy, "actual": closed_won, "total_pipeline": total_pipeline}], + "average_accuracy_percent": accuracy, + } forecast_control_center = ForecastControlCenter() diff --git a/salesflow-saas/backend/app/services/saudi_sensitive_workflow.py b/salesflow-saas/backend/app/services/saudi_sensitive_workflow.py index 6a529047..c35d0878 100644 --- a/salesflow-saas/backend/app/services/saudi_sensitive_workflow.py +++ b/salesflow-saas/backend/app/services/saudi_sensitive_workflow.py @@ -125,24 +125,50 @@ class SaudiSensitiveWorkflow: } async def _check_consent(self, db: AsyncSession, *, tenant_id: str, purpose: str) -> Dict[str, Any]: - """Check PDPL consent for data sharing purpose.""" + """Check PDPL consent — queries real PDPLConsent table.""" + from app.models.consent import PDPLConsent + from sqlalchemy import select, func + + total = int( + (await db.execute( + select(func.count()).select_from(PDPLConsent).where(PDPLConsent.tenant_id == tenant_id) + )).scalar() or 0 + ) + active = int( + (await db.execute( + select(func.count()).select_from(PDPLConsent) + .where(PDPLConsent.tenant_id == tenant_id, PDPLConsent.status == "granted") + )).scalar() or 0 + ) + + consent_valid = active > 0 or total == 0 # allow if no consent records exist yet (new tenant) return { - "consent_valid": True, - "consent_type": "legitimate_interest", + "consent_valid": consent_valid, + "consent_type": "explicit" if active > 0 else "not_found", "purpose": purpose, + "total_records": total, + "active_consents": active, "expires_at": None, - "note_ar": "موافقة سارية — المصلحة المشروعة", + "note_ar": "موافقة سارية" if consent_valid else "لا توجد موافقة PDPL سارية — مطلوب الحصول على موافقة", } def _check_export_rules(self, classification: Dict, partner_name: str) -> Dict[str, Any]: - """Check PDPL cross-border transfer rules.""" + """Check PDPL cross-border transfer rules — enforces based on classification.""" gcc_countries = {"SA", "AE", "BH", "KW", "OM", "QA"} + has_restricted = classification.get("highest_classification") == "restricted" + requires_dpo = classification.get("requires_dpo_review", False) + + # Restricted data requires explicit DPO review — block by default + export_allowed = not (has_restricted and requires_dpo) + return { - "export_allowed": True, + "export_allowed": export_allowed, "partner_jurisdiction": "SA", "gcc_transfer": True, - "restricted_data_present": classification.get("highest_classification") == "restricted", - "note_ar": "النقل مسموح ضمن دول مجلس التعاون", + "restricted_data_present": has_restricted, + "requires_dpo_review": requires_dpo, + "blocked_reason_ar": "بيانات مقيدة تتطلب مراجعة مسؤول حماية البيانات" if not export_allowed else None, + "note_ar": "النقل مسموح" if export_allowed else "النقل محظور — بيانات مقيدة", } async def _create_approval(