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>
This commit is contained in:
zlwuandCursor committed 2026-06-28 21:00:12 +08:00
1 parent ee531e9cfe
commit bf73b6b2d8
15 files changed
+598 -82

No files matched your search

+78 -16
View File
@@ -17,6 +17,8 @@ from app.core.tenant_context import get_tenant_no
from app.crud.crm.data_scope_helpers import (
crm_user_info,
ensure_crm_create_by_row_in_scope,
ensure_crm_contact_row_in_scope,
ensure_crm_customer_linked_row_in_scope,
ensure_crm_owner_row_in_scope,
)
from app.utils.crm_export_columns import (
@@ -329,6 +331,8 @@ async def category_tree(
"""获取产品分类树"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_info = crm_user_info(_user)
payload = body or {}
category_name = payload.get("categoryName") or payload.get("category_name")
status = payload.get("status")
@@ -337,6 +341,7 @@ async def category_tree(
tenant_no,
category_name=category_name,
status=status,
user_info=user_info,
)
return success_response(rows)
except ValueError as e:
@@ -516,6 +521,7 @@ async def category_add(
"""新增产品分类"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
data = body.model_dump(exclude_unset=True)
obj = create_category(db, tenant_no=tenant_no, user_id=user_id, **data)
@@ -537,7 +543,7 @@ async def category_update(
user_id = _user.get("user_id", "") or _user.get("id", "")
data = body.model_dump(exclude_unset=True)
category_id = data.pop("id")
obj = update_category(db, category_id, user_id=user_id, **data)
obj = update_category(db, category_id, user_id=user_id, user_info=user_info, **data)
return success_response(_category_row_to_dict(obj), message="更新成功")
except ValueError as e:
return error_response(400, str(e))
@@ -553,7 +559,7 @@ async def category_delete(
):
"""删除产品分类"""
try:
soft_delete_category(db, body.id)
soft_delete_category(db, body.id, user_info=user_info)
return success_response(None, message="删除成功")
except ValueError as e:
return error_response(400, str(e))
@@ -573,6 +579,7 @@ async def product_list(
"""分页查询产品列表"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
rows, total = list_products(
db,
tenant_no,
@@ -581,6 +588,7 @@ async def product_list(
keyword=body.keyword,
category_id=body.category_id,
status=body.status,
user_info=user_info,
)
tp = total_pages(total, body.pageSize)
return success_response(
@@ -606,9 +614,12 @@ async def product_detail(
):
"""获取产品详情"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
obj = get_product(db, body.id)
if not obj:
return error_response(404, "产品不存在")
ensure_crm_create_by_row_in_scope(db, obj, user_info, tenant_no, message="无权访问该产品")
return success_response(_product_row_to_dict(obj))
except ValueError as e:
return error_response(400, str(e))
@@ -625,6 +636,7 @@ async def product_add(
"""新增产品"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
obj = create_product(
db,
@@ -650,7 +662,9 @@ async def product_update(
user_id = _user.get("user_id", "") or _user.get("id", "")
data = body.model_dump(exclude_unset=True)
product_id = data.pop("id")
obj = update_product(db, product_id, user_id=user_id, **data)
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
obj = update_product(db, product_id, user_id=user_id, user_info=user_info, **data)
return success_response(_product_row_to_dict(obj), message="更新成功")
except ValueError as e:
return error_response(400, str(e))
@@ -666,6 +680,8 @@ async def product_delete(
):
"""删除产品(支持单条 body.id 和批量 body.ids)"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
ids_to_delete: List[str] = []
if body.ids:
ids_to_delete = body.ids
@@ -675,7 +691,7 @@ async def product_delete(
return error_response(400, "请传入 id 或 ids")
for pid in ids_to_delete:
soft_delete_product(db, pid)
soft_delete_product(db, pid, user_info=user_info)
return success_response(None, message="删除成功")
except ValueError as e:
@@ -692,10 +708,12 @@ async def product_update_status(
):
"""批量上架/下架"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
status_label = "上架" if body.status == "on" else "下架"
for pid in body.ids:
update_product(db, pid, user_id=user_id, status=body.status)
update_product(db, pid, user_id=user_id, user_info=user_info, status=body.status)
return success_response(None, message=f"批量{status_label}成功")
except ValueError as e:
return error_response(400, str(e))
@@ -712,11 +730,13 @@ async def product_options(
"""获取产品下拉选项"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
options = list_product_options(
db,
tenant_no,
keyword=body.keyword,
category_id=body.category_id,
user_info=user_info,
)
return success_response(options)
except ValueError as e:
@@ -739,6 +759,7 @@ async def product_import(
from openpyxl import load_workbook
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
content = await file.read()
@@ -833,6 +854,7 @@ async def product_export(
"""Excel 导出产品列表"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
rows, _ = list_products(
db,
tenant_no,
@@ -1424,10 +1446,11 @@ async def contact_list(
"""联系人分页列表"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
page = body.page if body else 1
page_size = body.pageSize if body else 10
rows, total = list_contacts(
db, tenant_no, page=page, page_size=page_size,
db, tenant_no, page=page, page_size=page_size, user_info=user_info,
keyword=body.keyword if body else None,
contact_name=body.contact_name if body else None,
mobile=body.mobile if body else None,
@@ -1461,9 +1484,12 @@ async def contact_detail(
contact_id = body.get("id")
if not contact_id:
return error_response(400, "缺少联系人ID")
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
row = get_contact(db, contact_id)
if not row:
return error_response(404, "联系人不存在")
ensure_crm_contact_row_in_scope(db, row, user_info, tenant_no, message="无权访问该联系人")
return success_response(contact_row_to_dict(row))
except ValueError as e:
return error_response(400, str(e))
@@ -1480,10 +1506,11 @@ async def contact_by_customer(
"""按客户ID查询联系人"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
customer_id = body.get("customerId", "")
if not customer_id:
return error_response(400, "缺少客户ID")
rows = list_contacts_by_customer(db, tenant_no, customer_id)
rows = list_contacts_by_customer(db, tenant_no, customer_id, user_info=user_info)
return success_response(rows)
except ValueError as e:
return error_response(400, str(e))
@@ -1500,9 +1527,11 @@ async def contact_add(
"""新增联系人"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
user_info = crm_user_info(_user)
obj = create_contact(
db, tenant_no=tenant_no, user_id=user_id,
db, tenant_no=tenant_no, user_id=user_id, user_info=user_info,
**body.model_dump(exclude_unset=True),
)
return success_response(contact_row_to_dict(obj), message="创建成功")
@@ -1523,7 +1552,9 @@ async def contact_update(
user_id = _user.get("user_id", "") or _user.get("id", "")
data = body.model_dump(exclude_unset=True)
contact_id = data.pop("id")
obj = update_contact(db, contact_id, user_id=user_id, **data)
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
obj = update_contact(db, contact_id, user_id=user_id, tenant_no=tenant_no, user_info=user_info, **data)
return success_response(contact_row_to_dict(obj), message="更新成功")
except ValueError as e:
return error_response(400, str(e))
@@ -1539,8 +1570,11 @@ async def contact_delete(
):
"""删除联系人"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
for cid in body.ids:
soft_delete_contact(db, cid)
soft_delete_contact(db, cid, user_id=user_id, tenant_no=tenant_no, user_info=user_info)
return success_response(None, message="删除成功")
except ValueError as e:
return error_response(400, str(e))
@@ -1560,7 +1594,9 @@ async def contact_set_primary(
if not contact_id:
return error_response(400, "缺少联系人ID")
user_id = _user.get("user_id", "") or _user.get("id", "")
obj = set_primary_contact(db, contact_id, user_id=user_id)
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
obj = set_primary_contact(db, contact_id, user_id=user_id, tenant_no=tenant_no, user_info=user_info)
return success_response(contact_row_to_dict(obj), message="设置成功")
except ValueError as e:
return error_response(400, str(e))
@@ -1577,8 +1613,9 @@ async def contact_export(
"""导出联系人列表"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
rows, _ = list_contacts(
db, tenant_no, page=1, page_size=10000,
db, tenant_no, page=1, page_size=10000, user_info=user_info,
keyword=body.keyword if body else None,
customer_id=body.customer_id if body else None,
)
@@ -1606,10 +1643,11 @@ async def contact_decision(
"""决策人联系人列表"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
page = body.page if body else 1
page_size = body.pageSize if body else 10
rows, total = list_contacts(
db, tenant_no, page=page, page_size=page_size,
db, tenant_no, page=page, page_size=page_size, user_info=user_info,
is_decision=True,
keyword=body.keyword if body else None,
)
@@ -1636,10 +1674,11 @@ async def contact_primary(
"""主联系人列表"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
page = body.page if body else 1
page_size = body.pageSize if body else 10
rows, total = list_contacts(
db, tenant_no, page=page, page_size=page_size,
db, tenant_no, page=page, page_size=page_size, user_info=user_info,
is_primary=True,
keyword=body.keyword if body else None,
)
@@ -1666,10 +1705,11 @@ async def contact_birthday(
"""本月生日联系人列表"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
page = body.page if body else 1
page_size = body.pageSize if body else 10
rows, total = list_contacts(
db, tenant_no, page=page, page_size=page_size,
db, tenant_no, page=page, page_size=page_size, user_info=user_info,
birthday_month=datetime.now().month,
keyword=body.keyword if body else None,
)
@@ -3044,10 +3084,12 @@ async def supplier_info_list(
"""分页查询供方列表 — Java: CrmSupplierInfoController.list"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
list_body = body or SupplierInfoListBody()
rows, total = list_supplier_info(
db,
tenant_no,
user_info=user_info,
page=list_body.page,
page_size=list_body.pageSize,
supplier_name=list_body.supplier_name,
@@ -3077,10 +3119,12 @@ async def supplier_info_list_all(
"""供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
list_body = body or SupplierInfoListAllBody()
rows = list_all_supplier_info(
db,
tenant_no,
user_info=user_info,
supplier_name=list_body.supplier_name,
status=list_body.status,
)
@@ -3100,9 +3144,13 @@ async def supplier_info_detail(
"""供方详情 — Java: CrmSupplierInfoController.getInfo"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
obj = get_supplier_info(db, body.id, tenant_no=tenant_no)
if not obj:
return error_response(404, "供方信息不存在")
ensure_crm_create_by_row_in_scope(
db, obj, user_info, tenant_no, message="无权访问该供方信息"
)
return success_response(supplier_info_row_to_dict(obj))
except ValueError as e:
return error_response(400, str(e))
@@ -3119,6 +3167,7 @@ async def supplier_info_add(
"""新增供方 — Java: CrmSupplierInfoController.add"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
obj = create_supplier_info(
@@ -3144,6 +3193,7 @@ async def supplier_info_update(
"""修改供方 — Java: CrmSupplierInfoController.edit"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
data = body.model_dump(exclude_unset=True)
supplier_id = data.pop("id")
@@ -3152,6 +3202,7 @@ async def supplier_info_update(
supplier_id,
tenant_no=tenant_no,
user_id=user_id,
user_info=user_info,
**data,
)
return success_response(supplier_info_row_to_dict(obj), message="修改成功")
@@ -3170,6 +3221,7 @@ async def supplier_info_delete(
"""删除供方 — Java: CrmSupplierInfoController.remove"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
ids_to_delete: List[str] = []
if body.ids:
@@ -3198,10 +3250,12 @@ async def supplier_info_export(
"""导出供方列表 — Java: CrmSupplierInfoController.export"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
list_body = body or SupplierInfoListBody()
rows, _ = list_supplier_info(
db,
tenant_no,
user_info=user_info,
page=1,
page_size=10000,
supplier_name=list_body.supplier_name,
@@ -3231,7 +3285,10 @@ async def _invoice_info_by_customer_impl(
db: Session,
):
tenant_no = get_tenant_no(db, _user)
rows = list_invoice_info_by_customer(db, tenant_no, body.customer_id)
user_info = crm_user_info(_user)
rows = list_invoice_info_by_customer(
db, tenant_no, body.customer_id, user_info=user_info
)
return success_response(rows)
@@ -3274,11 +3331,13 @@ async def invoice_info_add(
"""新增开票信息"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
obj = create_invoice_info(
db,
tenant_no=tenant_no,
user_id=user_id,
user_info=user_info,
**body.model_dump(exclude_unset=True),
)
return success_response(invoice_info_row_to_dict(obj), message="创建成功")
@@ -3297,6 +3356,7 @@ async def invoice_info_update(
"""更新开票信息"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
data = body.model_dump(exclude_unset=True)
invoice_id = data.pop("id")
@@ -3305,6 +3365,7 @@ async def invoice_info_update(
invoice_id,
tenant_no=tenant_no,
user_id=user_id,
user_info=user_info,
**data,
)
return success_response(invoice_info_row_to_dict(obj), message="更新成功")
@@ -3346,6 +3407,7 @@ async def invoice_info_set_default(
"""设置默认开票信息"""
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
obj = set_default_invoice_info(
db,
+45 -1
View File
@@ -11,6 +11,7 @@ from app.core.database import get_db
from app.core.menu_guard import require_admin_or_super_admin
from app.core.response import error_response, success_response
from app.core.tenant_context import get_tenant_no
from app.crud.crm.data_scope_helpers import crm_user_info
from app.crud.crm.contract import (
batch_assign_owner,
batch_submit_contract_approval,
@@ -99,6 +100,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
query = ContractListQuery(
pageNum=pageNum,
@@ -131,6 +133,7 @@ def register_contract_routes(router):
rows, total = list_contracts(
db,
tenant_no,
user_info=user_info,
page=query.pageNum,
page_size=query.pageSize,
filters=filters,
@@ -160,6 +163,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
detail = create_contract(
@@ -183,6 +187,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
data = body.model_dump(by_alias=True)
@@ -194,6 +199,7 @@ def register_contract_routes(router):
data=data,
user_id=user_id,
dept_id=dept_id,
user_info=user_info,
)
return success_response(detail, message="修改成功")
except ValueError as e:
@@ -209,6 +215,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
id_list = [x.strip() for x in ids.split(",") if x.strip()]
soft_delete_contracts(db, id_list, tenant_no=tenant_no)
return success_response(None, message="删除成功")
@@ -225,6 +232,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
submit_contract_approval(
@@ -235,6 +243,7 @@ def register_contract_routes(router):
remark=body.remark,
user_id=user_id,
dept_id=dept_id,
user_info=user_info,
)
return success_response(None, message="提交成功")
except ValueError as e:
@@ -250,6 +259,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
count = batch_submit_contract_approval(
@@ -260,6 +270,7 @@ def register_contract_routes(router):
remark=body.remark,
user_id=user_id,
dept_id=dept_id,
user_info=user_info,
)
return success_response(count, message="批量提交完成")
except ValueError as e:
@@ -275,6 +286,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
withdraw_contract_approval(
db,
@@ -282,6 +294,7 @@ def register_contract_routes(router):
tenant_no=tenant_no,
remark=body.remark,
user_id=user_id,
user_info=user_info,
)
return success_response(None, message="撤回成功")
except ValueError as e:
@@ -297,6 +310,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
start_execute(db, body.id, tenant_no=tenant_no, remark=body.remark, user_id=user_id)
return success_response(None, message="操作成功")
@@ -313,6 +327,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
finish_contract(db, body.id, tenant_no=tenant_no, remark=body.remark, user_id=user_id)
return success_response(None, message="操作成功")
@@ -329,6 +344,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
invalid_contract(
db,
@@ -337,6 +353,7 @@ def register_contract_routes(router):
invalid_reason=body.invalidReason,
remark=body.remark,
user_id=user_id,
user_info=user_info,
)
return success_response(None, message="作废成功")
except ValueError as e:
@@ -352,12 +369,14 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
save_template(
db,
body.id,
body.templateContent,
tenant_no=tenant_no,
settlement_clause_drafts=body.settlementClauseDrafts,
user_info=user_info,
)
return success_response(None, message="保存成功")
except ValueError as e:
@@ -373,6 +392,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
content = get_template(db, contract_id, tenant_no=tenant_no)
return success_response(content or "", message="获取模板成功")
except ValueError as e:
@@ -388,6 +408,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
upload_file(db, body.id, body.fileUrl, tenant_no=tenant_no)
return success_response(None, message="上传成功")
except ValueError as e:
@@ -403,9 +424,11 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
if body.attachments:
upload_attachments_with_category(
db, body.id, body.attachments, tenant_no=tenant_no
db, body.id, body.attachments, tenant_no=tenant_no,
user_info=user_info,
)
elif body.ossIds:
upload_attachments(db, body.id, body.ossIds, tenant_no=tenant_no)
@@ -425,6 +448,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
list_body = body or ContractExportBody()
filters = list_body.model_dump(
by_alias=True,
@@ -456,6 +480,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
req = ContractStatisticsOverviewQuery(
ownerUserId=ownerUserId,
beginTime=beginTime,
@@ -467,6 +492,7 @@ def register_contract_routes(router):
owner_user_id=req.ownerUserId,
begin_time=req.beginTime,
end_time=req.endTime,
user_info=user_info,
)
return success_response(data)
except ValueError as e:
@@ -481,6 +507,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
return success_response(statistics_by_status(db, tenant_no))
except ValueError as e:
return error_response(400, str(e))
@@ -494,6 +521,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
return success_response(statistics_by_type(db, tenant_no))
except ValueError as e:
return error_response(400, str(e))
@@ -510,6 +538,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
req = ContractStatisticsTrendQuery(type=type, beginTime=beginTime, endTime=endTime)
data = statistics_trend(
db,
@@ -517,6 +546,7 @@ def register_contract_routes(router):
period_type=req.type or "month",
begin_time=req.beginTime,
end_time=req.endTime,
user_info=user_info,
)
return success_response(data)
except ValueError as e:
@@ -532,6 +562,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
rows = query_expiring(db, tenant_no, days=days)
return success_response(rows)
except ValueError as e:
@@ -548,6 +579,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
new_id = renew_contract(
@@ -573,6 +605,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
contract_id = create_from_quote(
@@ -598,6 +631,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
contract_id = create_from_opportunity(
@@ -622,6 +656,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
sign_contract(
db,
@@ -631,6 +666,7 @@ def register_contract_routes(router):
contract_file=body.contractFile,
remark=body.remark or "",
user_id=user_id,
user_info=user_info,
)
return success_response(None, message="签订成功")
except ValueError as e:
@@ -646,6 +682,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
count = batch_assign_owner(
db,
@@ -654,6 +691,7 @@ def register_contract_routes(router):
owner_user_id=body.ownerUserId,
remark=body.remark or "",
user_id=user_id,
user_info=user_info,
)
return success_response(count, message="分配成功")
except ValueError as e:
@@ -669,6 +707,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
file_path = download_contract_pdf(db, contract_id, tenant_no=tenant_no)
return success_response(file_path)
except ValueError as e:
@@ -684,6 +723,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
approve_contract_by_contract_id(
db,
@@ -693,6 +733,7 @@ def register_contract_routes(router):
attachments=body.attachments or "",
remark=body.remark or "",
user_id=user_id,
user_info=user_info,
)
db.commit()
return success_response(None, message="审批成功")
@@ -709,6 +750,7 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
rows = list_contract_approvals(db, contract_id, tenant_no=tenant_no)
return success_response(rows)
except ValueError as e:
@@ -724,12 +766,14 @@ def register_contract_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
detail = get_contract_detail(
db,
contract_id,
tenant_no=tenant_no,
current_user_id=user_id,
user_info=user_info,
)
return success_response(detail)
except ValueError as e:
+28 -3
View File
@@ -11,6 +11,7 @@ from app.core.database import get_db
from app.core.menu_guard import require_admin_or_super_admin
from app.core.response import error_response, success_response
from app.core.tenant_context import get_tenant_no
from app.crud.crm.data_scope_helpers import crm_user_info
from app.crud.crm.contract import (
create_contract_change,
create_contract_template,
@@ -82,6 +83,7 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
query = ContractChangeListQuery(
pageNum=pageNum,
pageSize=pageSize,
@@ -96,6 +98,7 @@ def register_contract_sub_routes(router):
rows, total = list_contract_changes(
db,
tenant_no,
user_info=user_info,
page=query.pageNum,
page_size=query.pageSize,
filters=query.model_dump(by_alias=True, exclude={"pageNum", "pageSize"}, exclude_none=True),
@@ -123,7 +126,8 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
detail = get_contract_change_detail(db, change_id, tenant_no=tenant_no)
user_info = crm_user_info(_user)
detail = get_contract_change_detail(db, change_id, tenant_no=tenant_no, user_info=user_info)
return success_response(detail)
except ValueError as e:
return error_response(400, str(e))
@@ -138,6 +142,7 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
detail = create_contract_change(
@@ -161,6 +166,7 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
data = body.model_dump(by_alias=True)
change_id = data.pop("id")
@@ -170,6 +176,7 @@ def register_contract_sub_routes(router):
tenant_no=tenant_no,
data=data,
user_id=user_id,
user_info=user_info,
)
return success_response(detail, message="修改成功")
except ValueError as e:
@@ -185,8 +192,9 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
id_list = [x.strip() for x in ids.split(",") if x.strip()]
soft_delete_contract_changes(db, id_list, tenant_no=tenant_no)
soft_delete_contract_changes(db, id_list, tenant_no=tenant_no, user_info=user_info)
return success_response(None, message="删除成功")
except ValueError as e:
return error_response(400, str(e))
@@ -201,6 +209,7 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
submit_change_approval(
db,
@@ -224,6 +233,7 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
withdraw_change(db, change_id, tenant_no=tenant_no, user_id=user_id)
return success_response(None, message="撤回成功")
@@ -240,6 +250,7 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
execute_change(db, change_id, tenant_no=tenant_no, user_id=user_id)
return success_response(None, message="执行成功")
@@ -256,6 +267,7 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
list_body = body or ContractChangeExportBody()
filters = list_body.model_dump(
by_alias=True,
@@ -293,6 +305,7 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
query = ContractTemplateListQuery(
pageNum=pageNum,
pageSize=pageSize,
@@ -306,6 +319,7 @@ def register_contract_sub_routes(router):
rows, total = list_contract_templates(
db,
tenant_no,
user_info=user_info,
page=query.pageNum,
page_size=query.pageSize,
filters=query.model_dump(by_alias=True, exclude={"pageNum", "pageSize"}, exclude_none=True),
@@ -333,6 +347,7 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
detail = get_contract_template_detail(db, template_id, tenant_no=tenant_no)
return success_response(detail)
except ValueError as e:
@@ -348,6 +363,7 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
detail = create_contract_template(
@@ -371,6 +387,7 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
data = body.model_dump(by_alias=True)
template_id = data.pop("id")
@@ -380,6 +397,7 @@ def register_contract_sub_routes(router):
tenant_no=tenant_no,
data=data,
user_id=user_id,
user_info=user_info,
)
return success_response(detail, message="修改成功")
except ValueError as e:
@@ -395,8 +413,9 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
id_list = [x.strip() for x in ids.split(",") if x.strip()]
soft_delete_contract_templates(db, id_list, tenant_no=tenant_no)
soft_delete_contract_templates(db, id_list, tenant_no=tenant_no, user_info=user_info)
return success_response(None, message="删除成功")
except ValueError as e:
return error_response(400, str(e))
@@ -411,6 +430,7 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
toggle_contract_template_active(
db, template_id, tenant_no=tenant_no, is_active=1, user_id=user_id
@@ -429,6 +449,7 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
toggle_contract_template_active(
db, template_id, tenant_no=tenant_no, is_active=0, user_id=user_id
@@ -447,6 +468,7 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
list_body = body or ContractTemplateExportBody()
filters = list_body.model_dump(
by_alias=True,
@@ -485,6 +507,7 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
query = ContractApprovalListQuery(
pageNum=pageNum,
pageSize=pageSize,
@@ -526,6 +549,7 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
detail = get_contract_approval_detail(db, approval_id, tenant_no=tenant_no)
return success_response(detail)
except ValueError as e:
@@ -595,6 +619,7 @@ def register_contract_sub_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
list_body = body or ContractApprovalExportBody()
filters = list_body.model_dump(
by_alias=True,
+44 -1
View File
@@ -10,6 +10,7 @@ from app.core.database import get_db
from app.core.menu_guard import require_admin_or_super_admin
from app.core.response import error_response, success_response
from app.core.tenant_context import get_tenant_no
from app.crud.crm.data_scope_helpers import crm_user_info
from app.crud.crm.quote import (
accept_quote,
create_new_version,
@@ -88,11 +89,13 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
list_body = body or QuoteListBody()
user_id = _user.get("user_id", "") or _user.get("id", "")
rows, total = list_quotes(
db,
tenant_no,
user_info=user_info,
page=list_body.page,
page_size=list_body.pageSize,
filters=list_body.model_dump(by_alias=True, exclude={"page", "pageSize"}),
@@ -122,7 +125,8 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
detail = get_quote_detail(db, body.id, tenant_no=tenant_no)
user_info = crm_user_info(_user)
detail = get_quote_detail(db, body.id, tenant_no=tenant_no, user_info=user_info)
return success_response(detail)
except ValueError as e:
return error_response(400, str(e))
@@ -137,6 +141,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
detail = create_quote(
@@ -160,6 +165,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
data = body.model_dump(by_alias=True)
@@ -171,6 +177,7 @@ def register_quote_routes(router):
data=data,
user_id=user_id,
dept_id=dept_id,
user_info=user_info,
)
return success_response(detail, message="修改成功")
except ValueError as e:
@@ -186,6 +193,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
soft_delete_quotes(db, body.ids, tenant_no=tenant_no)
return success_response(None, message="删除成功")
except ValueError as e:
@@ -201,12 +209,14 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
submit_quote(
db,
body.id,
tenant_no=tenant_no,
approver_ids=body.approverIds,
remark=body.remark,
user_info=user_info,
)
return success_response(None, message="提交成功")
except ValueError as e:
@@ -222,6 +232,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
save_template(db, body.id, body.templateContent, tenant_no=tenant_no)
return success_response(None, message="保存成功")
except ValueError as e:
@@ -237,6 +248,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
content = get_template(db, body.id, tenant_no=tenant_no)
return success_response(content or "")
except ValueError as e:
@@ -252,6 +264,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
pdf_url = generate_pdf(db, body.id, tenant_no=tenant_no)
return success_response(pdf_url)
except ValueError as e:
@@ -267,12 +280,14 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
link = generate_share(
db,
body.id,
tenant_no=tenant_no,
validity_days=body.validityDays,
password=body.password or "",
user_info=user_info,
)
return success_response(link)
except ValueError as e:
@@ -288,6 +303,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
accept_quote(db, body.id, tenant_no=tenant_no, remark=body.remark)
return success_response(None, message="操作成功")
except ValueError as e:
@@ -303,12 +319,14 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
reject_quote(
db,
body.id,
tenant_no=tenant_no,
reject_reason=body.rejectReason,
remark=body.remark,
user_info=user_info,
)
return success_response(None, message="操作成功")
except ValueError as e:
@@ -324,6 +342,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
contract_id = to_contract(
@@ -337,6 +356,7 @@ def register_quote_routes(router):
end_date=body.endDate,
user_id=user_id,
dept_id=dept_id,
user_info=user_info,
)
return success_response(contract_id)
except ValueError as e:
@@ -352,6 +372,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
new_id = create_new_version(
@@ -362,6 +383,7 @@ def register_quote_routes(router):
remark=body.remark,
user_id=user_id,
dept_id=dept_id,
user_info=user_info,
)
return success_response(new_id)
except ValueError as e:
@@ -377,6 +399,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
rows = query_versions(db, body.quoteNo, tenant_no=tenant_no)
return success_response(rows)
except ValueError as e:
@@ -392,6 +415,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
invalid_quote(db, body.id, tenant_no=tenant_no, remark=body.remark)
return success_response(None, message="作废成功")
except ValueError as e:
@@ -407,10 +431,12 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
list_body = body or QuoteListBody()
rows, _ = list_quotes(
db,
tenant_no,
user_info=user_info,
page=1,
page_size=10000,
filters=list_body.model_dump(by_alias=True, exclude={"page", "pageSize"}),
@@ -437,6 +463,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
req = body or QuoteStatisticsOverviewBody()
data = statistics_overview(
db,
@@ -444,6 +471,7 @@ def register_quote_routes(router):
owner_user_id=req.ownerUserId,
begin_time=req.beginTime,
end_time=req.endTime,
user_info=user_info,
)
return success_response(data)
except ValueError as e:
@@ -458,6 +486,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
return success_response(statistics_status(db, tenant_no))
except ValueError as e:
return error_response(400, str(e))
@@ -472,6 +501,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
req = body or QuoteStatisticsTrendBody()
data = statistics_trend(
db,
@@ -479,6 +509,7 @@ def register_quote_routes(router):
period_type=req.type or "month",
begin_time=req.beginTime,
end_time=req.endTime,
user_info=user_info,
)
return success_response(data)
except ValueError as e:
@@ -494,6 +525,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
rows = list_quote_products(db, body.quoteId, tenant_no=tenant_no)
return success_response(rows)
except ValueError as e:
@@ -509,6 +541,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
obj = insert_quote_product(
@@ -534,6 +567,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
data = body.model_dump(by_alias=True)
line_id = data.pop("id")
@@ -543,6 +577,7 @@ def register_quote_routes(router):
tenant_no=tenant_no,
data=data,
user_id=user_id,
user_info=user_info,
)
from app.crud.crm.quote_product import product_row_to_dict
@@ -560,6 +595,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
soft_delete_quote_products(db, body.ids, tenant_no=tenant_no)
return success_response(None, message="删除成功")
except ValueError as e:
@@ -575,6 +611,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
rows = list_quote_approvals(db, body.quoteId, tenant_no=tenant_no)
return success_response(rows)
except ValueError as e:
@@ -590,6 +627,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
approve_quote(
db,
@@ -599,6 +637,7 @@ def register_quote_routes(router):
approval_opinion=body.approvalOpinion or "",
remark=body.remark or "",
user_id=user_id,
user_info=user_info,
)
return success_response(None, message="审批成功")
except ValueError as e:
@@ -614,6 +653,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
user_id = _user.get("user_id", "") or _user.get("id", "")
withdraw_approval(
db,
@@ -621,6 +661,7 @@ def register_quote_routes(router):
tenant_no=tenant_no,
remark=body.remark or "",
user_id=user_id,
user_info=user_info,
)
return success_response(None, message="撤回成功")
except ValueError as e:
@@ -651,6 +692,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
rows = list_share_logs(db, body.quoteId, tenant_no=tenant_no)
return success_response(rows)
except ValueError as e:
@@ -666,6 +708,7 @@ def register_quote_routes(router):
):
try:
tenant_no = get_tenant_no(db, _user)
user_info = crm_user_info(_user)
stats = share_log_statistics(db, body.quoteId, tenant_no=tenant_no)
return success_response(stats)
except ValueError as e:
+56 -4
View File
@@ -9,6 +9,11 @@ from sqlalchemy import func, or_
from sqlalchemy.orm import Session
from app.core.utils import generate_uuid
from app.crud.crm.data_scope_helpers import (
apply_crm_create_by_scope,
ensure_crm_contact_row_in_scope,
ensure_crm_owner_row_in_scope,
)
from app.models.crm_models import CrmContact
@@ -86,11 +91,14 @@ def list_contacts(
is_decision: bool = None,
birthday_month: int = None,
role: str = None,
user_info=None,
) -> Tuple[List[Dict[str, Any]], int]:
q = db.query(CrmContact).filter(
CrmContact.tenant_no == tenant_no,
CrmContact.del_flag == 0,
)
if user_info:
q = apply_crm_create_by_scope(q, CrmContact, user_info, db, tenant_no)
kw = (keyword or "").strip()
if kw:
@@ -146,7 +154,18 @@ def list_contacts_by_customer(
db: Session,
tenant_no: str,
customer_id: str,
*,
user_info=None,
) -> List[Dict[str, Any]]:
if user_info:
from app.crud.crm.customer import get_customer
customer = get_customer(db, customer_id)
if not customer or customer.tenant_no != tenant_no:
raise ValueError("客户不存在")
ensure_crm_owner_row_in_scope(
db, customer, user_info, tenant_no, message="无权访问该客户"
)
rows = (
db.query(CrmContact)
.filter(
@@ -177,14 +196,22 @@ def create_contact(
tenant_no: str,
user_id: str = None,
dept_id: str = "",
user_info=None,
**kwargs,
) -> CrmContact:
customer_id = kwargs.get("customer_id")
customer_name = kwargs.get("customer_name")
if customer_id and not customer_name:
if customer_id:
from app.crud.crm.customer import get_customer
cust = get_customer(db, customer_id)
if cust:
if user_info:
if not cust or cust.tenant_no != tenant_no:
raise ValueError("客户不存在")
ensure_crm_owner_row_in_scope(
db, cust, user_info, tenant_no, message="无权访问该客户"
)
if cust and not customer_name:
customer_name = cust.customer_name
obj = CrmContact(
@@ -229,11 +256,16 @@ def update_contact(
contact_id: str,
*,
user_id: str = None,
tenant_no: str = None,
user_info=None,
**kwargs,
) -> CrmContact:
obj = get_contact(db, contact_id)
if not obj:
raise ValueError("联系人不存在")
if tenant_no and obj.tenant_no != tenant_no:
raise ValueError("联系人不存在")
ensure_crm_contact_row_in_scope(db, obj, user_info, obj.tenant_no)
_updatable = (
"contact_name", "gender", "mobile", "telephone", "email",
@@ -260,10 +292,20 @@ def update_contact(
return obj
def set_primary_contact(db: Session, contact_id: str, user_id: str = None) -> CrmContact:
def set_primary_contact(
db: Session,
contact_id: str,
user_id: str = None,
*,
tenant_no: str = None,
user_info=None,
) -> CrmContact:
obj = get_contact(db, contact_id)
if not obj:
raise ValueError("联系人不存在")
if tenant_no and obj.tenant_no != tenant_no:
raise ValueError("联系人不存在")
ensure_crm_contact_row_in_scope(db, obj, user_info, obj.tenant_no)
db.query(CrmContact).filter(
CrmContact.customer_id == obj.customer_id,
@@ -279,10 +321,20 @@ def set_primary_contact(db: Session, contact_id: str, user_id: str = None) -> Cr
return obj
def soft_delete_contact(db: Session, contact_id: str, user_id: str = None) -> bool:
def soft_delete_contact(
db: Session,
contact_id: str,
user_id: str = None,
*,
tenant_no: str = None,
user_info=None,
) -> bool:
obj = get_contact(db, contact_id)
if not obj:
raise ValueError("联系人不存在")
if tenant_no and obj.tenant_no != tenant_no:
raise ValueError("联系人不存在")
ensure_crm_contact_row_in_scope(db, obj, user_info, obj.tenant_no)
customer_id = obj.customer_id
obj.del_flag = 1
obj.update_by = user_id
+103 -26
View File
@@ -13,6 +13,7 @@ from sqlalchemy import func, or_
from sqlalchemy.orm import Query, Session
from app.core.utils import generate_uuid
from app.crud.crm.data_scope_helpers import apply_crm_owner_scope, ensure_crm_owner_row_in_scope, apply_crm_create_by_scope, ensure_crm_create_by_row_in_scope
from app.crud.crm.contact import get_contact
from app.crud.crm.contract_approval import (
can_user_approve,
@@ -42,6 +43,25 @@ from app.models.crm_models import (
from app.models.models import SysUser
_FMT = "%Y-%m-%d %H:%M:%S"
def _ensure_contract_in_scope(db: Session, row: CrmContract, user_info, tenant_no: str) -> None:
ensure_crm_owner_row_in_scope(db, row, user_info, tenant_no, message="无权访问该合同")
def _get_contract_row_in_scope(
db: Session,
contract_id: str,
*,
tenant_no: str,
user_info=None,
) -> CrmContract:
row = get_contract_row(db, contract_id, tenant_no=tenant_no)
if not row:
raise ValueError("合同不存在")
_ensure_contract_in_scope(db, row, user_info, tenant_no)
return row
_DATE_FMT = "%Y-%m-%d"
@@ -374,10 +394,13 @@ def list_contracts(
page_size: int = 10,
filters: dict = None,
current_user_id: str = None,
user_info=None,
) -> Tuple[List[Dict[str, Any]], int]:
"""Java: queryPageList"""
q = db.query(CrmContract)
q = _build_contract_filters(db, q, filters or {}, tenant_no=tenant_no)
if user_info:
q = apply_crm_owner_scope(q, CrmContract, user_info, db, tenant_no)
total = q.order_by(None).with_entities(func.count(CrmContract.id)).scalar() or 0
page = max(1, page)
page_size = min(max(1, page_size), 100)
@@ -403,6 +426,7 @@ def get_contract(
*,
tenant_no: str,
current_user_id: str = None,
user_info=None,
) -> Optional[Dict[str, Any]]:
"""Java: queryById"""
row = (
@@ -416,6 +440,7 @@ def get_contract(
)
if not row:
return None
_ensure_contract_in_scope(db, row, user_info, tenant_no)
detail = _row_to_dict(row)
detail["createByName"] = _resolve_user_display_name(db, detail.get("createBy"))
@@ -506,12 +531,14 @@ def get_contract_detail(
*,
tenant_no: str,
current_user_id: str = None,
user_info=None,
) -> Dict[str, Any]:
detail = get_contract(
db,
contract_id,
tenant_no=tenant_no,
current_user_id=current_user_id,
user_info=user_info,
)
if not detail:
raise ValueError("合同不存在")
@@ -957,9 +984,10 @@ def update_contract(
data: dict,
user_id: str = None,
dept_id: str = "",
user_info=None,
) -> Dict[str, Any]:
"""Java: updateByBo"""
old = get_contract_row(db, contract_id, tenant_no=tenant_no)
old = _get_contract_row_in_scope(db, contract_id, tenant_no=tenant_no, user_info=user_info)
if not old:
raise ValueError("合同不存在")
if old.status != "draft":
@@ -1026,11 +1054,12 @@ def soft_delete_contracts(
*,
tenant_no: str,
validate: bool = True,
user_info=None,
) -> int:
"""Java: deleteWithValidByIds — 软删除"""
count = 0
for cid in contract_ids:
entity = get_contract_row(db, cid, tenant_no=tenant_no)
entity = _get_contract_row_in_scope(db, cid, tenant_no=tenant_no, user_info=user_info)
if not entity:
continue
if validate and entity.status != "draft":
@@ -1116,9 +1145,10 @@ def submit_contract_approval(
remark: str = "",
user_id: str = None,
dept_id: str = "",
user_info=None,
) -> bool:
"""Java: submitApproval"""
entity = get_contract_row(db, contract_id, tenant_no=tenant_no)
entity = _get_contract_row_in_scope(db, contract_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("合同不存在")
if entity.status != "draft":
@@ -1158,6 +1188,7 @@ def batch_submit_contract_approval(
remark: str = "",
user_id: str = None,
dept_id: str = "",
user_info=None,
) -> int:
"""Java: batchSubmitApproval"""
success = 0
@@ -1185,9 +1216,10 @@ def withdraw_contract_approval(
tenant_no: str,
remark: str = "",
user_id: str = None,
user_info=None,
) -> bool:
"""Java: withdrawApproval"""
entity = get_contract_row(db, contract_id, tenant_no=tenant_no)
entity = _get_contract_row_in_scope(db, contract_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("合同不存在")
if entity.status != "approving":
@@ -1211,8 +1243,9 @@ def start_execute(
tenant_no: str,
remark: str = "",
user_id: str = None,
user_info=None,
) -> bool:
entity = get_contract_row(db, contract_id, tenant_no=tenant_no)
entity = _get_contract_row_in_scope(db, contract_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("合同不存在")
if entity.status != "approved":
@@ -1233,8 +1266,9 @@ def finish_contract(
tenant_no: str,
remark: str = "",
user_id: str = None,
user_info=None,
) -> bool:
entity = get_contract_row(db, contract_id, tenant_no=tenant_no)
entity = _get_contract_row_in_scope(db, contract_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("合同不存在")
if entity.status != "executing":
@@ -1256,10 +1290,11 @@ def invalid_contract(
invalid_reason: str,
remark: str = "",
user_id: str = None,
user_info=None,
) -> bool:
if not (invalid_reason or "").strip():
raise ValueError("作废原因不能为空")
entity = get_contract_row(db, contract_id, tenant_no=tenant_no)
entity = _get_contract_row_in_scope(db, contract_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("合同不存在")
if entity.status == "finished":
@@ -1282,8 +1317,9 @@ def save_template(
*,
tenant_no: str,
settlement_clause_drafts: str = None,
user_info=None,
) -> bool:
entity = get_contract_row(db, contract_id, tenant_no=tenant_no)
entity = _get_contract_row_in_scope(db, contract_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("合同不存在")
@@ -1305,7 +1341,7 @@ def save_template(
def get_template(db: Session, contract_id: str, *, tenant_no: str) -> str:
entity = get_contract_row(db, contract_id, tenant_no=tenant_no)
entity = _get_contract_row_in_scope(db, contract_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("合同不存在")
return entity.template_content or ""
@@ -1317,8 +1353,9 @@ def upload_file(
file_url: str,
*,
tenant_no: str,
user_info=None,
) -> bool:
entity = get_contract_row(db, contract_id, tenant_no=tenant_no)
entity = _get_contract_row_in_scope(db, contract_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("合同不存在")
entity.contract_file = file_url or ""
@@ -1333,10 +1370,11 @@ def upload_attachments(
oss_ids: str,
*,
tenant_no: str,
user_info=None,
) -> bool:
if not (oss_ids or "").strip():
raise ValueError("附件ossId不能为空")
entity = get_contract_row(db, contract_id, tenant_no=tenant_no)
entity = _get_contract_row_in_scope(db, contract_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("合同不存在")
@@ -1357,10 +1395,11 @@ def upload_attachments_with_category(
attachments: List[dict],
*,
tenant_no: str,
user_info=None,
) -> bool:
if not attachments:
raise ValueError("附件列表不能为空")
entity = get_contract_row(db, contract_id, tenant_no=tenant_no)
entity = _get_contract_row_in_scope(db, contract_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("合同不存在")
@@ -1404,6 +1443,7 @@ def statistics_overview(
owner_user_id: str = None,
begin_time: str = None,
end_time: str = None,
user_info=None,
) -> Dict[str, Any]:
q = db.query(CrmContract).filter(CrmContract.tenant_no == tenant_no, CrmContract.del_flag == 0)
if owner_user_id:
@@ -1416,6 +1456,8 @@ def statistics_overview(
q = q.filter(CrmContract.create_time >= begin_dt)
elif end_dt:
q = q.filter(CrmContract.create_time <= end_dt)
if user_info:
q = apply_crm_owner_scope(q, CrmContract, user_info, db, tenant_no)
contracts = q.all()
total_count = len(contracts)
@@ -1457,12 +1499,11 @@ def statistics_overview(
}
def statistics_by_status(db: Session, tenant_no: str) -> List[Dict[str, Any]]:
contracts = (
db.query(CrmContract)
.filter(CrmContract.tenant_no == tenant_no, CrmContract.del_flag == 0)
.all()
)
def statistics_by_status(db: Session, tenant_no: str, *, user_info=None) -> List[Dict[str, Any]]:
q = db.query(CrmContract).filter(CrmContract.tenant_no == tenant_no, CrmContract.del_flag == 0)
if user_info:
q = apply_crm_owner_scope(q, CrmContract, user_info, db, tenant_no)
contracts = q.all()
total_count = len(contracts)
total_amount = sum(_decimal_value(c.amount) for c in contracts)
@@ -1492,12 +1533,11 @@ def statistics_by_status(db: Session, tenant_no: str) -> List[Dict[str, Any]]:
return result
def statistics_by_type(db: Session, tenant_no: str) -> List[Dict[str, Any]]:
contracts = (
db.query(CrmContract)
.filter(CrmContract.tenant_no == tenant_no, CrmContract.del_flag == 0)
.all()
)
def statistics_by_type(db: Session, tenant_no: str, *, user_info=None) -> List[Dict[str, Any]]:
q = db.query(CrmContract).filter(CrmContract.tenant_no == tenant_no, CrmContract.del_flag == 0)
if user_info:
q = apply_crm_owner_scope(q, CrmContract, user_info, db, tenant_no)
contracts = q.all()
total_count = len(contracts)
type_map = (
("sale", "销售合同"),
@@ -1544,12 +1584,15 @@ def statistics_trend(
period_type: str = "month",
begin_time: str = None,
end_time: str = None,
user_info=None,
) -> List[Dict[str, Any]]:
q = db.query(CrmContract).filter(CrmContract.tenant_no == tenant_no, CrmContract.del_flag == 0)
begin_dt = _parse_optional_datetime(begin_time)
end_dt = _parse_optional_datetime(end_time)
if begin_dt and end_dt:
q = q.filter(CrmContract.create_time >= begin_dt, CrmContract.create_time <= end_dt)
if user_info:
q = apply_crm_owner_scope(q, CrmContract, user_info, db, tenant_no)
contracts = q.all()
if not contracts:
return []
@@ -1585,6 +1628,7 @@ def query_expiring(
tenant_no: str,
*,
days: int = 30,
user_info=None,
) -> List[Dict[str, Any]]:
if not days or days <= 0:
days = 30
@@ -1642,6 +1686,7 @@ def create_from_quote(
data: dict,
user_id: str = None,
dept_id: str = "",
user_info=None,
) -> str:
"""Java: createFromQuote — 返回合同 id"""
quote = get_quote(db, quote_id, tenant_no=tenant_no)
@@ -1700,6 +1745,7 @@ def renew_contract(
data: dict,
user_id: str = None,
dept_id: str = "",
user_info=None,
) -> str:
"""Java: renewContract — 基础续签"""
parent = get_contract_row(db, parent_contract_id, tenant_no=tenant_no)
@@ -1750,6 +1796,7 @@ def export_contracts(
tenant_no: str,
*,
filters: dict = None,
user_info=None,
) -> List[Dict[str, Any]]:
"""Java: queryList for export"""
q = db.query(CrmContract)
@@ -1790,11 +1837,14 @@ def list_contract_changes(
page: int = 1,
page_size: int = 10,
filters: dict = None,
user_info=None,
) -> Tuple[List[Dict[str, Any]], int]:
q = db.query(CrmContractChange).filter(
CrmContractChange.tenant_no == tenant_no,
CrmContractChange.del_flag == 0,
)
if user_info:
q = apply_crm_create_by_scope(q, CrmContractChange, user_info, db, tenant_no)
filters = filters or {}
contract_id = (filters.get("contractId") or filters.get("contract_id") or "").strip()
if contract_id:
@@ -1814,6 +1864,7 @@ def get_contract_change_detail(
change_id: str,
*,
tenant_no: str,
user_info=None,
) -> Dict[str, Any]:
row = (
db.query(CrmContractChange)
@@ -1826,6 +1877,9 @@ def get_contract_change_detail(
)
if not row:
raise ValueError("合同变更记录不存在")
ensure_crm_create_by_row_in_scope(
db, row, user_info, tenant_no, message="无权访问该合同变更"
)
return _change_row_to_dict(row)
@@ -1861,11 +1915,14 @@ def list_contract_templates(
page: int = 1,
page_size: int = 10,
filters: dict = None,
user_info=None,
) -> Tuple[List[Dict[str, Any]], int]:
q = db.query(CrmContractTemplate).filter(
CrmContractTemplate.tenant_no == tenant_no,
CrmContractTemplate.del_flag == 0,
)
if user_info:
q = apply_crm_create_by_scope(q, CrmContractTemplate, user_info, db, tenant_no)
filters = filters or {}
template_no = (filters.get("templateNo") or filters.get("template_no") or "").strip()
if template_no:
@@ -1937,6 +1994,7 @@ def create_from_opportunity(
data: dict,
user_id: str = None,
dept_id: str = "",
user_info=None,
) -> str:
"""Java: createFromOpportunity — 返回合同 id"""
opp_row = (
@@ -1990,9 +2048,10 @@ def sign_contract(
contract_file: str,
remark: str = "",
user_id: str = None,
user_info=None,
) -> bool:
"""Java: signContract"""
entity = get_contract_row(db, contract_id, tenant_no=tenant_no)
entity = _get_contract_row_in_scope(db, contract_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("合同不存在")
if entity.status != "approved":
@@ -2019,6 +2078,7 @@ def batch_assign_owner(
owner_user_id: str,
remark: str = "",
user_id: str = None,
user_info=None,
) -> int:
"""Java: batchAssignOwner"""
if not contract_ids:
@@ -2030,7 +2090,7 @@ def batch_assign_owner(
success = 0
owner_name = _resolve_user_display_name(db, owner_id)
for cid in contract_ids:
entity = get_contract_row(db, cid, tenant_no=tenant_no)
entity = _get_contract_row_in_scope(db, cid, tenant_no=tenant_no, user_info=user_info)
if not entity:
continue
entity.owner_user_id = owner_id
@@ -2049,6 +2109,7 @@ def download_contract_pdf(
contract_id: str,
*,
tenant_no: str,
user_info=None,
) -> str:
"""Java: downloadContractPdf — 返回合同文件 URL/路径"""
detail = get_contract_detail(db, contract_id, tenant_no=tenant_no)
@@ -2177,10 +2238,14 @@ def update_contract_change(
tenant_no: str,
data: dict,
user_id: str = None,
user_info=None,
) -> Dict[str, Any]:
entity = _get_change_row(db, change_id, tenant_no=tenant_no)
if not entity:
raise ValueError("合同变更记录不存在")
ensure_crm_create_by_row_in_scope(
db, entity, user_info, tenant_no, message="无权访问该合同变更"
)
if entity.approval_status != "pending":
raise ValueError("只能修改待审批状态的变更记录")
@@ -2210,12 +2275,16 @@ def soft_delete_contract_changes(
*,
tenant_no: str,
validate: bool = True,
user_info=None,
) -> int:
count = 0
for cid in change_ids:
entity = _get_change_row(db, cid, tenant_no=tenant_no)
if not entity:
continue
ensure_crm_create_by_row_in_scope(
db, entity, user_info, tenant_no, message="无权访问该合同变更"
)
if validate and entity.approval_status != "pending":
raise ValueError("只能删除待审批状态的变更记录")
entity.del_flag = 1
@@ -2346,6 +2415,7 @@ def update_contract_template(
tenant_no: str,
data: dict,
user_id: str = None,
user_info=None,
) -> Dict[str, Any]:
entity = (
db.query(CrmContractTemplate)
@@ -2358,6 +2428,9 @@ def update_contract_template(
)
if not entity:
raise ValueError("合同模板不存在")
ensure_crm_create_by_row_in_scope(
db, entity, user_info, tenant_no, message="无权访问该合同模板"
)
field_map = (
(("template_no", "templateNo"), "template_no"),
@@ -2398,6 +2471,7 @@ def soft_delete_contract_templates(
*,
tenant_no: str,
validate: bool = True,
user_info=None,
) -> int:
count = 0
for tid in template_ids:
@@ -2412,6 +2486,9 @@ def soft_delete_contract_templates(
)
if not entity:
continue
ensure_crm_create_by_row_in_scope(
db, entity, user_info, tenant_no, message="无权访问该合同模板"
)
if validate and (entity.usage_count or 0) > 0:
raise ValueError(f"模板【{entity.template_name}】已被使用,不能删除")
entity.del_flag = 1
+5
View File
@@ -76,7 +76,12 @@ def list_contract_products(
*,
tenant_no: str = None,
include_price_tiers: bool = False,
user_info=None,
) -> List[Dict[str, Any]]:
if tenant_no and user_info:
from app.crud.crm.contract import _get_contract_row_in_scope
_get_contract_row_in_scope(db, contract_id, tenant_no=tenant_no, user_info=user_info)
q = db.query(CrmContractProduct).filter(
CrmContractProduct.contract_id == contract_id,
CrmContractProduct.del_flag == 0,
@@ -52,3 +52,51 @@ def ensure_crm_create_by_row_in_scope(
return
if not row_in_data_scope(db, row, user_info, tenant_no):
raise ValueError(message)
def ensure_crm_contact_row_in_scope(
db: Session, row, user_info, tenant_no: str, *, message: str = "无权访问该联系人"
) -> None:
"""联系人:create_by 在范围内,或关联客户在 owner 范围内。"""
if not user_info or row is None:
return
if row_in_data_scope(db, row, user_info, tenant_no):
return
customer_id = getattr(row, "customer_id", None) or ""
if customer_id:
from app.crud.crm.customer import get_customer
customer = get_customer(db, customer_id)
if customer and customer.tenant_no == tenant_no:
if row_in_data_scope(
db, customer, user_info, tenant_no, owner_field=CRM_OWNER_FIELD
):
return
raise ValueError(message)
def ensure_crm_customer_linked_row_in_scope(
db: Session,
row,
user_info,
tenant_no: str,
*,
customer_id: str = None,
message: str = "无权访问该数据",
) -> None:
"""客户子表(开票信息等):先校验关联客户 owner scope,否则校验 create_by。"""
if not user_info or row is None:
return
cid = customer_id or getattr(row, "customer_id", None) or ""
if cid:
from app.crud.crm.customer import get_customer
customer = get_customer(db, cid)
if customer and customer.tenant_no == tenant_no:
if row_in_data_scope(
db, customer, user_info, tenant_no, owner_field=CRM_OWNER_FIELD
):
return
if row_in_data_scope(db, row, user_info, tenant_no):
return
raise ValueError(message)
+46 -1
View File
@@ -7,6 +7,10 @@ from typing import Any, Dict, List, Optional
from sqlalchemy.orm import Session
from app.core.utils import generate_uuid
from app.crud.crm.data_scope_helpers import (
ensure_crm_customer_linked_row_in_scope,
ensure_crm_owner_row_in_scope,
)
from app.models.crm_models import CrmInvoiceInfo
@@ -39,7 +43,18 @@ def list_invoice_info_by_customer(
db: Session,
tenant_no: str,
customer_id: str,
*,
user_info=None,
) -> List[Dict[str, Any]]:
if user_info:
from app.crud.crm.customer import get_customer
customer = get_customer(db, customer_id)
if not customer or customer.tenant_no != tenant_no:
raise ValueError("客户不存在")
ensure_crm_owner_row_in_scope(
db, customer, user_info, tenant_no, message="无权访问该客户"
)
rows = (
db.query(CrmInvoiceInfo)
.filter(
@@ -87,11 +102,21 @@ def create_invoice_info(
tenant_no: str,
user_id: str = None,
dept_id: str = "",
user_info=None,
**kwargs,
) -> CrmInvoiceInfo:
customer_id = kwargs.get("customer_id")
if not customer_id:
raise ValueError("缺少客户ID")
if user_info:
from app.crud.crm.customer import get_customer
customer = get_customer(db, customer_id)
if not customer or customer.tenant_no != tenant_no:
raise ValueError("客户不存在")
ensure_crm_owner_row_in_scope(
db, customer, user_info, tenant_no, message="无权访问该客户"
)
company_name = (kwargs.get("company_name") or "").strip()
if not company_name:
@@ -132,6 +157,7 @@ def update_invoice_info(
*,
tenant_no: str = None,
user_id: str = None,
user_info=None,
**kwargs,
) -> CrmInvoiceInfo:
obj = get_invoice_info(db, invoice_id)
@@ -140,6 +166,9 @@ def update_invoice_info(
if tenant_no and obj.tenant_no != tenant_no:
raise ValueError("开票信息不存在")
ensure_crm_customer_linked_row_in_scope(
db, obj, user_info, obj.tenant_no, message="无权访问该开票信息"
)
_updatable = (
"company_name",
@@ -181,12 +210,16 @@ def set_default_invoice_info(
*,
tenant_no: str,
user_id: str = None,
user_info=None,
) -> CrmInvoiceInfo:
obj = get_invoice_info(db, invoice_id)
if not obj or obj.tenant_no != tenant_no:
raise ValueError("开票信息不存在")
if obj.customer_id != customer_id:
raise ValueError("开票信息与客户不匹配")
ensure_crm_customer_linked_row_in_scope(
db, obj, user_info, tenant_no, message="无权访问该开票信息"
)
_clear_default_for_customer(db, tenant_no, customer_id, exclude_id=invoice_id)
obj.is_default = "1"
@@ -197,10 +230,22 @@ def set_default_invoice_info(
return obj
def soft_delete_invoice_info(db: Session, invoice_id: str, user_id: str = None) -> bool:
def soft_delete_invoice_info(
db: Session,
invoice_id: str,
user_id: str = None,
*,
tenant_no: str = None,
user_info=None,
) -> bool:
obj = get_invoice_info(db, invoice_id)
if not obj:
raise ValueError("开票信息不存在")
if tenant_no and obj.tenant_no != tenant_no:
raise ValueError("开票信息不存在")
ensure_crm_customer_linked_row_in_scope(
db, obj, user_info, obj.tenant_no, message="无权访问该开票信息"
)
obj.del_flag = 1
obj.update_by = user_id
obj.update_time = datetime.now()
+32 -3
View File
@@ -10,6 +10,10 @@ from sqlalchemy import and_, func, or_
from sqlalchemy.orm import Session
from app.core.utils import generate_uuid
from app.crud.crm.data_scope_helpers import (
apply_crm_create_by_scope,
ensure_crm_create_by_row_in_scope,
)
from app.models.crm_models import CrmProduct, CrmProductCategory, SysOss
@@ -48,12 +52,15 @@ def list_categories(
*,
category_name: Optional[str] = None,
status: Optional[str] = None,
user_info=None,
) -> List[Dict[str, Any]]:
"""获取分类(扁平列表,支持名称模糊与状态筛选)"""
q = db.query(CrmProductCategory).filter(
CrmProductCategory.tenant_no == tenant_no,
CrmProductCategory.del_flag == 0,
)
if user_info:
q = apply_crm_create_by_scope(q, CrmProductCategory, user_info, db, tenant_no)
name_kw = (category_name or "").strip()
if name_kw:
q = q.filter(CrmProductCategory.category_name.like(f"%{name_kw}%"))
@@ -99,6 +106,7 @@ def list_category_tree(
*,
category_name: Optional[str] = None,
status: Optional[str] = None,
user_info=None,
) -> List[Dict[str, Any]]:
"""获取产品分类树(嵌套 children,供树形表格/树选择器使用)"""
name_kw = (category_name or "").strip()
@@ -106,7 +114,7 @@ def list_category_tree(
has_filter = bool(name_kw) or bool(status_val)
if not has_filter:
flat = list_categories(db, tenant_no)
flat = list_categories(db, tenant_no, user_info=user_info)
return _build_category_tree(flat)
matched = list_categories(
@@ -114,6 +122,7 @@ def list_category_tree(
tenant_no,
category_name=category_name,
status=status,
user_info=user_info,
)
if not matched:
return []
@@ -178,12 +187,16 @@ def update_category(
category_id: str,
*,
user_id: str = None,
user_info=None,
**kwargs,
) -> CrmProductCategory:
"""更新分类"""
obj = get_category(db, category_id)
if not obj:
raise ValueError("分类不存在")
ensure_crm_create_by_row_in_scope(
db, obj, user_info, obj.tenant_no, message="无权访问该分类"
)
# 逐字段更新
for field in ("category_name", "category_code", "status", "order_num", "remark"):
@@ -208,11 +221,14 @@ def update_category(
return obj
def soft_delete_category(db: Session, category_id: str) -> None:
def soft_delete_category(db: Session, category_id: str, *, user_info=None) -> None:
"""软删除分类"""
obj = get_category(db, category_id)
if not obj:
raise ValueError("分类不存在")
ensure_crm_create_by_row_in_scope(
db, obj, user_info, obj.tenant_no, message="无权访问该分类"
)
# 检查是否有子分类
has_children = (
@@ -303,12 +319,15 @@ def list_products(
keyword: str = None,
category_id: str = None,
status: str = None,
user_info=None,
) -> Tuple[List[Dict[str, Any]], int]:
"""分页查询产品列表"""
q = db.query(CrmProduct).filter(
CrmProduct.tenant_no == tenant_no,
CrmProduct.del_flag == 0,
)
if user_info:
q = apply_crm_create_by_scope(q, CrmProduct, user_info, db, tenant_no)
# keyword 筛选
kw = (keyword or "").strip()
@@ -482,12 +501,16 @@ def update_product(
product_id: str,
*,
user_id: str = None,
user_info=None,
**kwargs,
) -> CrmProduct:
"""更新产品"""
obj = get_product(db, product_id)
if not obj:
raise ValueError("产品不存在")
ensure_crm_create_by_row_in_scope(
db, obj, user_info, obj.tenant_no, message="无权访问该产品"
)
# 逐字段更新
for field in (
@@ -535,11 +558,14 @@ def update_product(
return obj
def soft_delete_product(db: Session, product_id: str) -> None:
def soft_delete_product(db: Session, product_id: str, *, user_info=None) -> None:
"""软删除产品"""
obj = get_product(db, product_id)
if not obj:
raise ValueError("产品不存在")
ensure_crm_create_by_row_in_scope(
db, obj, user_info, obj.tenant_no, message="无权访问该产品"
)
obj.del_flag = 1
db.commit()
@@ -584,6 +610,7 @@ def list_product_options(
*,
keyword: str = None,
category_id: str = None,
user_info=None,
) -> List[Dict[str, Any]]:
"""获取产品下拉选项(不分页,返回全量)"""
q = db.query(CrmProduct).filter(
@@ -591,6 +618,8 @@ def list_product_options(
CrmProduct.del_flag == 0,
CrmProduct.status == "on",
)
if user_info:
q = apply_crm_create_by_scope(q, CrmProduct, user_info, db, tenant_no)
if keyword:
like = f"%{keyword}%"
q = q.filter(or_(CrmProduct.product_name.like(like), CrmProduct.product_no.like(like)))
+62 -26
View File
@@ -31,10 +31,28 @@ from app.crud.crm.supplier_info import get_supplier_info
from app.models.crm_models import CrmContract, CrmQuote, CrmQuoteApproval
from app.models.models import SysUser
from app.services.crm_quote_pdf import generate_quote_pdf
from app.crud.crm.data_scope_helpers import apply_crm_owner_scope, ensure_crm_owner_row_in_scope
_FMT = "%Y-%m-%d %H:%M:%S"
_DATE_FMT = "%Y-%m-%d"
def _ensure_quote_in_scope(db: Session, row: CrmQuote, user_info, tenant_no: str) -> None:
ensure_crm_owner_row_in_scope(db, row, user_info, tenant_no, message="无权访问该报价单")
def _get_quote_in_scope(
db: Session,
quote_id: str,
*,
tenant_no: str,
user_info=None,
) -> CrmQuote:
obj = get_quote(db, quote_id, tenant_no=tenant_no)
if not obj:
raise ValueError("报价单不存在")
_ensure_quote_in_scope(db, obj, user_info, tenant_no)
return obj
def total_pages(total: int, page_size: int) -> int:
if page_size <= 0:
@@ -467,11 +485,13 @@ def get_quote_detail(
quote_id: str,
*,
tenant_no: str = None,
user_info=None,
) -> Dict[str, Any]:
"""Java: queryById"""
obj = get_quote(db, quote_id, tenant_no=tenant_no)
if not obj:
raise ValueError("报价单不存在")
_ensure_quote_in_scope(db, obj, user_info, tenant_no or obj.tenant_no)
detail = _row_to_dict(obj)
detail["productList"] = list_quote_products(db, quote_id, tenant_no=tenant_no or obj.tenant_no)
@@ -513,10 +533,13 @@ def list_quotes(
page_size: int = 10,
filters: dict = None,
current_user_id: str = None,
user_info=None,
) -> Tuple[List[Dict[str, Any]], int]:
"""Java: queryPageList"""
q = db.query(CrmQuote)
q = _build_quote_filters(db, q, filters or {}, tenant_no=tenant_no)
if user_info:
q = apply_crm_owner_scope(q, CrmQuote, user_info, db, tenant_no)
total = q.order_by(None).with_entities(func.count(CrmQuote.id)).scalar() or 0
page = max(1, page)
page_size = min(max(1, page_size), 100)
@@ -645,9 +668,10 @@ def update_quote(
data: dict,
user_id: str = None,
dept_id: str = "",
user_info=None,
) -> Dict[str, Any]:
"""Java: updateByBo"""
old = get_quote(db, quote_id, tenant_no=tenant_no)
old = _get_quote_in_scope(db, quote_id, tenant_no=tenant_no, user_info=user_info)
if not old:
raise ValueError("报价单不存在")
if old.status != "draft":
@@ -769,11 +793,12 @@ def soft_delete_quotes(
*,
tenant_no: str,
validate: bool = True,
user_info=None,
) -> int:
"""Java: deleteWithValidByIds"""
count = 0
for qid in quote_ids:
entity = get_quote(db, qid, tenant_no=tenant_no)
entity = _get_quote_in_scope(db, qid, tenant_no=tenant_no, user_info=user_info)
if not entity:
continue
if validate:
@@ -797,9 +822,10 @@ def submit_quote(
remark: str = "",
user_id: str = None,
dept_id: str = "",
user_info=None,
) -> bool:
"""Java: submitQuote"""
entity = get_quote(db, quote_id, tenant_no=tenant_no)
entity = _get_quote_in_scope(db, quote_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("报价单不存在")
if entity.status != "draft":
@@ -837,9 +863,10 @@ def save_template(
template_content: str,
*,
tenant_no: str,
user_info=None,
) -> bool:
"""Java: saveTemplate"""
entity = get_quote(db, quote_id, tenant_no=tenant_no)
entity = _get_quote_in_scope(db, quote_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("报价单不存在")
entity.template_content = template_content
@@ -853,9 +880,10 @@ def get_template(
quote_id: str,
*,
tenant_no: str,
user_info=None,
) -> str:
"""Java: getTemplate"""
entity = get_quote(db, quote_id, tenant_no=tenant_no)
entity = _get_quote_in_scope(db, quote_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("报价单不存在")
return entity.template_content or ""
@@ -866,9 +894,10 @@ def generate_pdf(
quote_id: str,
*,
tenant_no: str,
user_info=None,
) -> str:
"""Java: generatePdf"""
entity = get_quote(db, quote_id, tenant_no=tenant_no)
entity = _get_quote_in_scope(db, quote_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("报价单不存在")
detail = get_quote_detail(db, quote_id, tenant_no=tenant_no)
@@ -887,9 +916,10 @@ def generate_share(
tenant_no: str,
validity_days: int = None,
password: str = "",
user_info=None,
) -> str:
"""Java: generateShare"""
entity = get_quote(db, quote_id, tenant_no=tenant_no)
entity = _get_quote_in_scope(db, quote_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("报价单不存在")
@@ -966,9 +996,10 @@ def accept_quote(
*,
tenant_no: str,
remark: str = "",
user_info=None,
) -> bool:
"""Java: acceptQuote"""
entity = get_quote(db, quote_id, tenant_no=tenant_no)
entity = _get_quote_in_scope(db, quote_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("报价单不存在")
entity.status = "accepted"
@@ -986,9 +1017,10 @@ def reject_quote(
tenant_no: str,
reject_reason: str = "",
remark: str = "",
user_info=None,
) -> bool:
"""Java: rejectQuote"""
entity = get_quote(db, quote_id, tenant_no=tenant_no)
entity = _get_quote_in_scope(db, quote_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("报价单不存在")
entity.status = "rejected"
@@ -1012,6 +1044,7 @@ def to_contract(
end_date: date = None,
user_id: str = None,
dept_id: str = "",
user_info=None,
) -> str:
"""Java: toContract — 返回 contractId"""
sign_date = _parse_optional_date(sign_date)
@@ -1075,9 +1108,10 @@ def create_new_version(
remark: str = "",
user_id: str = None,
dept_id: str = "",
user_info=None,
) -> str:
"""Java: createNewVersion — 返回新报价单 id"""
old = get_quote(db, quote_id, tenant_no=tenant_no)
old = _get_quote_in_scope(db, quote_id, tenant_no=tenant_no, user_info=user_info)
if not old:
raise ValueError("报价单不存在")
@@ -1150,6 +1184,7 @@ def query_versions(
quote_no: str,
*,
tenant_no: str,
user_info=None,
) -> List[Dict[str, Any]]:
"""Java: queryVersions"""
rows = (
@@ -1172,9 +1207,10 @@ def withdraw_quote_approval(
tenant_no: str,
remark: str = "",
user_id: str = None,
user_info=None,
) -> bool:
"""Java: CrmQuoteServiceImpl.withdrawApproval(校验创建人)"""
entity = get_quote(db, quote_id, tenant_no=tenant_no)
entity = _get_quote_in_scope(db, quote_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("报价单不存在")
if entity.status != "submitted":
@@ -1197,9 +1233,10 @@ def invalid_quote(
*,
tenant_no: str,
remark: str = "",
user_info=None,
) -> bool:
"""Java: invalidQuote"""
entity = get_quote(db, quote_id, tenant_no=tenant_no)
entity = _get_quote_in_scope(db, quote_id, tenant_no=tenant_no, user_info=user_info)
if not entity:
raise ValueError("报价单不存在")
if entity.status == "invalid":
@@ -1221,6 +1258,7 @@ def statistics_overview(
owner_user_id: str = None,
begin_time: str = None,
end_time: str = None,
user_info=None,
) -> Dict[str, Any]:
"""Java: statisticsOverview"""
q = db.query(CrmQuote).filter(CrmQuote.tenant_no == tenant_no, CrmQuote.del_flag == 0)
@@ -1232,6 +1270,8 @@ def statistics_overview(
q = q.filter(CrmQuote.create_time >= begin_dt)
if end_dt:
q = q.filter(CrmQuote.create_time <= end_dt)
if user_info:
q = apply_crm_owner_scope(q, CrmQuote, user_info, db, tenant_no)
all_quotes = q.all()
total_count = len(all_quotes)
@@ -1273,28 +1313,21 @@ def statistics_overview(
def statistics_status(
db: Session,
tenant_no: str,
user_info=None,
) -> List[Dict[str, Any]]:
"""Java: statisticsStatus"""
status_array = ("draft", "submitted", "accepted", "rejected", "expired", "contract")
status_names = ("草稿", "已提交", "已接受", "已拒绝", "已过期", "已转合同")
total = (
db.query(CrmQuote)
.filter(CrmQuote.tenant_no == tenant_no, CrmQuote.del_flag == 0)
.count()
)
base_q = db.query(CrmQuote).filter(CrmQuote.tenant_no == tenant_no, CrmQuote.del_flag == 0)
if user_info:
base_q = apply_crm_owner_scope(base_q, CrmQuote, user_info, db, tenant_no)
total = base_q.count()
result = []
for status, status_name in zip(status_array, status_names):
rows = (
db.query(CrmQuote)
.filter(
CrmQuote.tenant_no == tenant_no,
CrmQuote.del_flag == 0,
CrmQuote.status == status,
)
.all()
)
q = base_q.filter(CrmQuote.status == status)
rows = q.all()
count = len(rows)
amount = sum(_decimal_value(r.final_amount) for r in rows)
percent = (count * 100.0 / total) if total else 0.0
@@ -1326,6 +1359,7 @@ def statistics_trend(
period_type: str = "month",
begin_time: str = None,
end_time: str = None,
user_info=None,
) -> List[Dict[str, Any]]:
"""Java: statisticsTrend"""
q = db.query(CrmQuote).filter(CrmQuote.tenant_no == tenant_no, CrmQuote.del_flag == 0)
@@ -1335,6 +1369,8 @@ def statistics_trend(
q = q.filter(CrmQuote.create_time >= begin_dt)
if end_dt:
q = q.filter(CrmQuote.create_time <= end_dt)
if user_info:
q = apply_crm_owner_scope(q, CrmQuote, user_info, db, tenant_no)
all_quotes = q.all()
if not all_quotes:
+5
View File
@@ -44,8 +44,13 @@ def list_quote_approvals(
quote_id: str,
*,
tenant_no: str = None,
user_info=None,
) -> List[Dict[str, Any]]:
"""Java: queryListByQuoteId(含审批人昵称)"""
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(CrmQuoteApproval).filter(
CrmQuoteApproval.quote_id == quote_id,
CrmQuoteApproval.del_flag == 0,
+18
View File
@@ -13,6 +13,13 @@ from app.models.crm_models import CrmQuoteProduct
_FMT = "%Y-%m-%d %H:%M:%S"
def _ensure_quote_parent_scope(db: Session, quote_id: str, tenant_no: str, user_info) -> None:
if 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)
def _decimal_value(value: Any, default: Decimal = Decimal("0")) -> Decimal:
if value is None:
@@ -76,8 +83,11 @@ def list_quote_products(
quote_id: str,
*,
tenant_no: str = None,
user_info=None,
) -> List[Dict[str, Any]]:
"""Java: queryListByQuoteId"""
if tenant_no and user_info:
_ensure_quote_parent_scope(db, quote_id, tenant_no, user_info)
q = db.query(CrmQuoteProduct).filter(
CrmQuoteProduct.quote_id == quote_id,
CrmQuoteProduct.del_flag == 0,
@@ -104,8 +114,10 @@ def insert_quote_product(
data: dict,
user_id: str = None,
dept_id: str = "",
user_info=None,
) -> CrmQuoteProduct:
"""Java: insertByBo"""
_ensure_quote_parent_scope(db, quote_id, tenant_no, user_info)
price = _decimal_value(_get_field(data, "price"), Decimal("0"))
quantity = _decimal_value(_get_field(data, "quantity"), Decimal("0"))
discount = _decimal_value(_get_field(data, "discount"), Decimal("0"))
@@ -169,11 +181,14 @@ def update_quote_product(
*,
data: dict,
user_id: str = None,
user_info=None,
) -> CrmQuoteProduct:
"""Java: updateByBo"""
entity = get_quote_product(db, product_line_id)
if not entity:
raise ValueError("报价产品明细不存在")
if user_info:
_ensure_quote_parent_scope(db, entity.quote_id, entity.tenant_no, user_info)
if "product_id" in data or "productId" in data:
entity.product_id = (_get_field(data, "product_id", "productId") or entity.product_id or "").strip()
@@ -217,6 +232,7 @@ def soft_delete_quote_products(
ids: List[str],
*,
validate_min_one: bool = True,
user_info=None,
) -> int:
"""Java: deleteWithValidByIds"""
count = 0
@@ -224,6 +240,8 @@ def soft_delete_quote_products(
entity = get_quote_product(db, pid)
if not entity:
continue
if user_info:
_ensure_quote_parent_scope(db, entity.quote_id, entity.tenant_no, user_info)
if validate_min_one:
sibling_count = (
db.query(CrmQuoteProduct)
+10 -1
View File
@@ -7,7 +7,6 @@ from typing import Any, Dict, List, Optional
from sqlalchemy.orm import Session
from app.core.utils import generate_uuid
from app.models.crm_models import CrmQuoteShareLog
_FMT = "%Y-%m-%d %H:%M:%S"
@@ -78,8 +77,13 @@ def list_share_logs(
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,
@@ -95,8 +99,13 @@ def share_log_statistics(
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,
+18
View File
@@ -8,6 +8,10 @@ from typing import Any, Dict, List, Optional, Tuple
from sqlalchemy.orm import Session
from app.core.utils import generate_uuid
from app.crud.crm.data_scope_helpers import (
apply_crm_create_by_scope,
ensure_crm_create_by_row_in_scope,
)
from app.models.crm_models import CrmSupplierInfo
_FMT = "%Y-%m-%d %H:%M:%S"
@@ -74,12 +78,15 @@ def list_supplier_info(
page_size: int = 10,
supplier_name: str = None,
status: str = None,
user_info=None,
) -> Tuple[List[Dict[str, Any]], int]:
"""Java: queryPageList"""
q = db.query(CrmSupplierInfo).filter(
CrmSupplierInfo.tenant_no == tenant_no,
CrmSupplierInfo.del_flag == 0,
)
if user_info:
q = apply_crm_create_by_scope(q, CrmSupplierInfo, user_info, db, tenant_no)
q = _apply_list_filters(q, supplier_name=supplier_name, status=status)
total = q.count()
rows = (
@@ -97,12 +104,15 @@ def list_all_supplier_info(
*,
supplier_name: str = None,
status: str = None,
user_info=None,
) -> List[Dict[str, Any]]:
"""Java: queryList(不分页,报价单下拉)"""
q = db.query(CrmSupplierInfo).filter(
CrmSupplierInfo.tenant_no == tenant_no,
CrmSupplierInfo.del_flag == 0,
)
if user_info:
q = apply_crm_create_by_scope(q, CrmSupplierInfo, user_info, db, tenant_no)
q = _apply_list_filters(q, supplier_name=supplier_name, status=status)
rows = q.order_by(CrmSupplierInfo.create_time.desc()).all()
return [_row_to_dict(r) for r in rows]
@@ -168,12 +178,16 @@ def update_supplier_info(
*,
tenant_no: str = None,
user_id: str = None,
user_info=None,
**kwargs,
) -> CrmSupplierInfo:
"""Java: updateByBo"""
obj = get_supplier_info(db, supplier_id, tenant_no=tenant_no)
if not obj:
raise ValueError("供方信息不存在")
ensure_crm_create_by_row_in_scope(
db, obj, user_info, obj.tenant_no, message="无权访问该供方信息"
)
if "supplier_name" in kwargs and kwargs["supplier_name"] is not None:
name = (kwargs["supplier_name"] or "").strip()
@@ -200,11 +214,15 @@ def soft_delete_supplier_info(
*,
tenant_no: str = None,
user_id: str = None,
user_info=None,
) -> bool:
"""Java: deleteWithValidByIds(门户软删)"""
obj = get_supplier_info(db, supplier_id, tenant_no=tenant_no)
if not obj:
raise ValueError("供方信息不存在")
ensure_crm_create_by_row_in_scope(
db, obj, user_info, obj.tenant_no, message="无权访问该供方信息"
)
obj.del_flag = 1
obj.update_by = user_id
obj.update_time = datetime.now()