Files
shanxia2.0/backend/app/crud/crm/quote_share_log.py
T
zlwuandCursor bf73b6b2d8 feat(crm): 联系人、产品、报价与合同域接入数据权限过滤
补全 PR3 Phase 2.4–2.7,列表 apply_data_scope、详情/CUD assert_row_in_data_scope,合同/报价主表支持 owner_user_id。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-28 21:00:12 +08:00

137 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""CRM 报价单分享访问日志 CRUD — 对照 CrmQuoteShareLogServiceImpl.java"""
from __future__ import annotations
import re
from datetime import datetime
from typing import Any, Dict, List, Optional
from sqlalchemy.orm import Session
from app.models.crm_models import CrmQuoteShareLog
_FMT = "%Y-%m-%d %H:%M:%S"
_MOBILE_UA_RE = re.compile(r"(mobile|android|iphone|ipad|ipod|blackberry|windows phone)", re.I)
def _row_to_dict(row: CrmQuoteShareLog) -> Dict[str, Any]:
return {
"id": row.id,
"quoteId": row.quote_id or "",
"shareCode": row.share_code or "",
"visitTime": row.visit_time.strftime(_FMT) if row.visit_time else "",
"ipAddress": row.ip_address or "",
"location": row.location or "",
"deviceType": row.device_type or "",
"browser": row.browser or "",
"stayDuration": row.stay_duration if row.stay_duration is not None else 0,
"pageViews": row.page_views if row.page_views is not None else 1,
"createTime": row.create_time.strftime(_FMT) if row.create_time else "",
}
def _parse_user_agent(user_agent: Optional[str]) -> tuple[str, str]:
"""简易 User-Agent 解析(替代 Hutool UserAgentUtil)"""
ua = (user_agent or "").strip()
if not ua:
return "Unknown", "Unknown"
device = "Mobile" if _MOBILE_UA_RE.search(ua) else "PC"
browser = ua[:100] if len(ua) > 100 else ua
return device, browser
def record_visit(
db: Session,
*,
tenant_no: str,
quote_id: str,
share_code: str,
ip_address: str = "",
user_agent: str = "",
user_id: str = None,
dept_id: str = "",
) -> bool:
"""Java: recordVisit"""
device_type, browser = _parse_user_agent(user_agent)
entity = CrmQuoteShareLog(
id=generate_uuid(),
quote_id=quote_id,
share_code=share_code,
visit_time=datetime.now(),
ip_address=ip_address or "",
device_type=device_type,
browser=browser,
stay_duration=0,
page_views=1,
tenant_no=tenant_no,
create_by=user_id,
create_dept_id=dept_id or "",
del_flag=0,
)
db.add(entity)
db.flush()
return True
def list_share_logs(
db: Session,
quote_id: str,
*,
tenant_no: str = None,
user_info=None,
) -> List[Dict[str, Any]]:
"""Java: queryLogsByQuoteId"""
if tenant_no and user_info:
from app.crud.crm.quote import _get_quote_in_scope
_get_quote_in_scope(db, quote_id, tenant_no=tenant_no, user_info=user_info)
q = db.query(CrmQuoteShareLog).filter(
CrmQuoteShareLog.quote_id == quote_id,
CrmQuoteShareLog.del_flag == 0,
)
if tenant_no:
q = q.filter(CrmQuoteShareLog.tenant_no == tenant_no)
rows = q.order_by(CrmQuoteShareLog.visit_time.desc()).all()
return [_row_to_dict(r) for r in rows]
def share_log_statistics(
db: Session,
quote_id: str,
*,
tenant_no: str = None,
user_info=None,
) -> Dict[str, Any]:
"""Java: statisticsVisits"""
if tenant_no and user_info:
from app.crud.crm.quote import _get_quote_in_scope
_get_quote_in_scope(db, quote_id, tenant_no=tenant_no, user_info=user_info)
q = db.query(CrmQuoteShareLog).filter(
CrmQuoteShareLog.quote_id == quote_id,
CrmQuoteShareLog.del_flag == 0,
)
if tenant_no:
q = q.filter(CrmQuoteShareLog.tenant_no == tenant_no)
logs = q.all()
ips = {log.ip_address for log in logs if (log.ip_address or "").strip()}
visit_times = [log.visit_time for log in logs if log.visit_time]
device_stats: Dict[str, int] = {}
for log in logs:
key = (log.device_type or "").strip() or "Unknown"
device_stats[key] = device_stats.get(key, 0) + 1
total_stay = sum(log.stay_duration or 0 for log in logs)
avg_stay = (total_stay / len(logs)) if logs else 0.0
return {
"totalVisits": len(logs),
"uniqueIps": len(ips),
"lastVisitTime": max(visit_times).strftime(_FMT) if visit_times else None,
"firstVisitTime": min(visit_times).strftime(_FMT) if visit_times else None,
"deviceStats": device_stats,
"totalStayDuration": total_stay,
"avgStayDuration": avg_stay,
}