提交
This commit is contained in:
1 parent
7b1d4c8729
commit
ffc89d9b57
75 files changed
+12263
-7950
No files matched your search
@@ -14,7 +14,7 @@
|
||||
# 与 sys_user.tenant_no 一致;登录按 (tenant_no, user_no) 查用户
|
||||
DEFAULT_TENANT_NO=shanxia2.0
|
||||
# 用户无 sys_user_role 记录时,Web 管理端 JWT 使用的默认角色(super_admin 可全量菜单)
|
||||
# WEB_AUTH_DEFAULT_ROLE=super_admin
|
||||
# WEB_AUTH_DEFAULT_ROLE=admin
|
||||
|
||||
# JWT配置示例
|
||||
# SECRET_KEY=your-secret-key-change-in-production
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
82692
|
||||
47712
|
||||
@@ -525,7 +525,6 @@ 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)
|
||||
@@ -544,6 +543,7 @@ def category_update(
|
||||
):
|
||||
"""更新产品分类"""
|
||||
try:
|
||||
user_info = crm_user_info(_user)
|
||||
user_id = _user.get("user_id", "") or _user.get("id", "")
|
||||
data = body.model_dump(exclude_unset=True)
|
||||
category_id = data.pop("id")
|
||||
@@ -563,6 +563,7 @@ def category_delete(
|
||||
):
|
||||
"""删除产品分类"""
|
||||
try:
|
||||
user_info = crm_user_info(_user)
|
||||
soft_delete_category(db, body.id, user_info=user_info)
|
||||
return success_response(None, message="删除成功")
|
||||
except ValueError as e:
|
||||
|
||||
@@ -398,7 +398,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)
|
||||
content = get_template(db, contract_id, tenant_no=tenant_no, user_info=user_info)
|
||||
return success_response(content or "", message="获取模板成功")
|
||||
except ValueError as e:
|
||||
return error_response(400, str(e))
|
||||
@@ -729,7 +729,6 @@ 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,
|
||||
@@ -739,7 +738,6 @@ 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="审批成功")
|
||||
|
||||
@@ -249,7 +249,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)
|
||||
content = get_template(db, body.id, tenant_no=tenant_no, user_info=user_info)
|
||||
return success_response(content or "")
|
||||
except ValueError as e:
|
||||
return error_response(400, str(e))
|
||||
@@ -627,7 +627,6 @@ 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,
|
||||
@@ -637,7 +636,6 @@ 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:
|
||||
@@ -653,7 +651,6 @@ 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,
|
||||
@@ -661,7 +658,6 @@ 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:
|
||||
|
||||
@@ -26,7 +26,11 @@ from app.models.models import SysUser
|
||||
from app.crud.user import user_crud
|
||||
from app.core.utils import generate_uuid
|
||||
from app.core import login_crypto
|
||||
from app.services.web_auth_roles import iter_user_menu_perm_codes, resolve_web_role_for_user
|
||||
from app.services.web_auth_roles import (
|
||||
iter_user_menu_perm_codes,
|
||||
resolve_web_role_for_user,
|
||||
user_has_bound_roles,
|
||||
)
|
||||
from app.crud.crm.quote import view_by_share_code
|
||||
|
||||
router = APIRouter(prefix="", tags=["公共接口"])
|
||||
@@ -257,6 +261,18 @@ def get_web_auth_menu_codes(request: Request, db: Session = Depends(get_db)):
|
||||
token = authorization.replace("Bearer ", "")
|
||||
try:
|
||||
current_user = get_current_user_by_token(token=token, db=db)
|
||||
user_id = current_user.get("user_id") or ""
|
||||
tenant_no = current_user.get("tenant_no")
|
||||
|
||||
if not user_has_bound_roles(db, user_id, tenant_no):
|
||||
return success_response(
|
||||
{
|
||||
"isSuperAdmin": False,
|
||||
"all": False,
|
||||
"menuCodes": [],
|
||||
}
|
||||
)
|
||||
|
||||
role = current_user.get("role")
|
||||
if role == "super_admin":
|
||||
return success_response(
|
||||
@@ -268,7 +284,7 @@ def get_web_auth_menu_codes(request: Request, db: Session = Depends(get_db)):
|
||||
)
|
||||
|
||||
try:
|
||||
codes = iter_user_menu_perm_codes(db, current_user.get("user_id") or "")
|
||||
codes = iter_user_menu_perm_codes(db, user_id, tenant_no)
|
||||
menu_codes = sorted(codes)
|
||||
except OperationalError:
|
||||
menu_codes = []
|
||||
|
||||
@@ -116,8 +116,8 @@ class Settings(BaseSettings):
|
||||
|
||||
# 与 docs/sql/shanxia2.0.sql 基线一致:登录按租户 + sys_user.user_no
|
||||
DEFAULT_TENANT_NO: str = "shanxia2.0"
|
||||
# 用户未绑定 sys_user_role 时,Web 管理端默认角色(建议 super_admin 便于开发全菜单)
|
||||
WEB_AUTH_DEFAULT_ROLE: str = "super_admin"
|
||||
# 无 user_id 等异常链路的 Web 角色兜底;未绑定 sys_user_role 的用户在 resolve_web_role_for_user 中固定为 admin
|
||||
WEB_AUTH_DEFAULT_ROLE: str = "admin"
|
||||
|
||||
# 登录 RSA:私钥 PEM(多实例/负载均衡须配置同一私钥;留空则进程启动时临时生成,仅适合单机开发)
|
||||
LOGIN_RSA_PRIVATE_KEY_PEM: str = ""
|
||||
|
||||
@@ -1342,7 +1342,13 @@ def save_template(
|
||||
return True
|
||||
|
||||
|
||||
def get_template(db: Session, contract_id: str, *, tenant_no: str) -> str:
|
||||
def get_template(
|
||||
db: Session,
|
||||
contract_id: str,
|
||||
*,
|
||||
tenant_no: str,
|
||||
user_info=None,
|
||||
) -> str:
|
||||
entity = _get_contract_row_in_scope(db, contract_id, tenant_no=tenant_no, user_info=user_info)
|
||||
if not entity:
|
||||
raise ValueError("合同不存在")
|
||||
|
||||
@@ -76,6 +76,8 @@ def _recalc_opportunity_follow_stats(db: Session, opportunity_id: str) -> None:
|
||||
)
|
||||
if not opp:
|
||||
return
|
||||
# 统计前 flush,确保当前事务内新增/软删的跟进记录已写入,避免 follow_count 少 1
|
||||
db.flush()
|
||||
remaining = (
|
||||
db.query(CrmOpportunityFollow)
|
||||
.filter(
|
||||
|
||||
@@ -384,32 +384,44 @@ def get_product_by_no(db: Session, product_no: str, tenant_no: str) -> Optional[
|
||||
)
|
||||
|
||||
|
||||
def generate_product_no(db: Session, tenant_no: str) -> str:
|
||||
"""生成产品编号(格式:CP + yyyyMMdd + 3位序号,如 CP20260530001)"""
|
||||
today_str = datetime.now().strftime("%Y%m%d")
|
||||
prefix = f"CP{today_str}"
|
||||
PRODUCT_NO_PREFIX = "SXWL"
|
||||
PRODUCT_NO_SEQ_LEN = 6
|
||||
PRODUCT_NO_GENERATE_MAX_RETRIES = 10
|
||||
PRODUCT_NO_SEQ_MAX = 10 ** PRODUCT_NO_SEQ_LEN - 1
|
||||
|
||||
# 查询今天已有的最大编号
|
||||
max_no = (
|
||||
|
||||
def _max_sxwl_product_seq(db: Session, tenant_no: str) -> int:
|
||||
"""取当前租户下 SXWL 前缀且后缀为纯数字的产品编号最大流水值"""
|
||||
prefix_len = len(PRODUCT_NO_PREFIX)
|
||||
rows = (
|
||||
db.query(CrmProduct.product_no)
|
||||
.filter(
|
||||
CrmProduct.product_no.like(f"{prefix}%"),
|
||||
CrmProduct.product_no.like(f"{PRODUCT_NO_PREFIX}%"),
|
||||
CrmProduct.tenant_no == tenant_no,
|
||||
)
|
||||
.order_by(CrmProduct.product_no.desc())
|
||||
.first()
|
||||
.all()
|
||||
)
|
||||
max_seq = 0
|
||||
for (product_no,) in rows:
|
||||
if not product_no or len(product_no) <= prefix_len:
|
||||
continue
|
||||
suffix = product_no[prefix_len:]
|
||||
if suffix.isdigit():
|
||||
max_seq = max(max_seq, int(suffix))
|
||||
return max_seq
|
||||
|
||||
if max_no and max_no[0]:
|
||||
seq_str = max_no[0][len(prefix):]
|
||||
try:
|
||||
seq = int(seq_str) + 1
|
||||
except ValueError:
|
||||
seq = 1
|
||||
else:
|
||||
seq = 1
|
||||
|
||||
return f"{prefix}{seq:03d}"
|
||||
def generate_product_no(db: Session, tenant_no: str) -> str:
|
||||
"""生成产品编号(格式:SXWL + 6位递增流水号,如 SXWL000001)"""
|
||||
seq = _max_sxwl_product_seq(db, tenant_no) + 1
|
||||
for _ in range(PRODUCT_NO_GENERATE_MAX_RETRIES):
|
||||
if seq > PRODUCT_NO_SEQ_MAX:
|
||||
raise ValueError("产品编号序号已达上限")
|
||||
candidate = f"{PRODUCT_NO_PREFIX}{seq:0{PRODUCT_NO_SEQ_LEN}d}"
|
||||
if not product_no_exists(db, candidate, tenant_no):
|
||||
return candidate
|
||||
seq += 1
|
||||
raise ValueError("自动生成产品编号失败,请稍后重试")
|
||||
|
||||
|
||||
def generate_barcode(db: Session, tenant_no: str) -> str:
|
||||
@@ -722,11 +734,6 @@ def soft_delete_oss(db: Session, oss_id: str) -> None:
|
||||
# ── 报价行产品 ID 解析(对照 CrmProductServiceImpl) ─────────────
|
||||
|
||||
|
||||
QUOTE_AUTO_PRODUCT_NO_PREFIX = "SXWL"
|
||||
QUOTE_AUTO_PRODUCT_NO_RANDOM_LEN = 8
|
||||
PRODUCT_NO_GENERATE_MAX_RETRIES = 10
|
||||
|
||||
|
||||
def _normalize_text(value: Any) -> Optional[str]:
|
||||
if value is None:
|
||||
return None
|
||||
@@ -742,19 +749,8 @@ def _get_quote_item_field(data: dict, *keys: str, default=None):
|
||||
|
||||
|
||||
def generate_auto_product_no(db: Session, tenant_no: str) -> str:
|
||||
"""Java: generateAutoProductNo — SXWL + 8位随机大写字符"""
|
||||
import secrets
|
||||
import string
|
||||
|
||||
alphabet = string.ascii_uppercase + string.digits
|
||||
for _ in range(PRODUCT_NO_GENERATE_MAX_RETRIES):
|
||||
suffix = "".join(
|
||||
secrets.choice(alphabet) for _ in range(QUOTE_AUTO_PRODUCT_NO_RANDOM_LEN)
|
||||
)
|
||||
candidate = f"{QUOTE_AUTO_PRODUCT_NO_PREFIX}{suffix}"
|
||||
if not product_no_exists(db, candidate, tenant_no):
|
||||
return candidate
|
||||
raise ValueError("自动生成产品编号失败,请稍后重试")
|
||||
"""报价自动建档时生成产品编号(与产品管理新增规则一致)"""
|
||||
return generate_product_no(db, tenant_no)
|
||||
|
||||
|
||||
def resolve_product_id_for_quote_item(
|
||||
|
||||
@@ -479,6 +479,7 @@ class PlTransportDbLink(Base):
|
||||
plan_product_id = Column(String(64), nullable=False, comment="计划产品行ID")
|
||||
db_purchase_plan_id = Column(String(64), nullable=False, comment="地磅计划ID")
|
||||
db_inventory_id = Column(String(64), nullable=False, comment="出入库单ID")
|
||||
contributed_qty = Column(DECIMAL(15, 3), nullable=True, comment="本次联动贡献量(吨)")
|
||||
tenant_no = Column(String(64), nullable=False, default="shanxia2.0", comment="租户编号")
|
||||
order_num = Column(Integer, nullable=True, comment="排序号")
|
||||
remark = Column(String(512), nullable=True, comment="备注")
|
||||
|
||||
@@ -94,7 +94,7 @@ class CustomerCreateBody(BaseModel):
|
||||
owner_user_id: Optional[str] = Field(default=None, alias="ownerUserId", description="负责人ID")
|
||||
business_license_img: Optional[str] = Field(
|
||||
default=None,
|
||||
validation_alias=AliasChoices("businessLicenseImg", "businessLicenseImages"),
|
||||
validation_alias=AliasChoices("businessLicenseImages", "businessLicenseImg"),
|
||||
serialization_alias="businessLicenseImages",
|
||||
description="营业执照图片URL",
|
||||
)
|
||||
@@ -158,7 +158,7 @@ class CustomerUpdateBody(BaseModel):
|
||||
owner_user_id: Optional[str] = Field(default=None, alias="ownerUserId", description="负责人ID")
|
||||
business_license_img: Optional[str] = Field(
|
||||
default=None,
|
||||
validation_alias=AliasChoices("businessLicenseImg", "businessLicenseImages"),
|
||||
validation_alias=AliasChoices("businessLicenseImages", "businessLicenseImg"),
|
||||
serialization_alias="businessLicenseImages",
|
||||
description="营业执照图片URL",
|
||||
)
|
||||
|
||||
@@ -71,7 +71,7 @@ class ProductCreateBody(BaseModel):
|
||||
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
|
||||
product_no: Optional[str] = Field(default=None, alias="productNo", description="产品编号(不传则自动生成)")
|
||||
product_no: Optional[str] = Field(default=None, alias="productNo", description="产品编号(不传则自动生成,格式 SXWL000001)")
|
||||
product_name: str = Field(..., alias="productName", description="产品名称")
|
||||
category_id: Optional[str] = Field(default=None, alias="categoryId", description="分类ID")
|
||||
category_name: Optional[str] = Field(default=None, alias="categoryName", description="分类名称")
|
||||
|
||||
@@ -46,6 +46,41 @@ def resolve_partner_company(plan: PlPlan, plan_type: str) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def _normalize_to_date(value: date | datetime | str | None) -> Optional[date]:
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, datetime):
|
||||
return value.date()
|
||||
if isinstance(value, date):
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
return parse_date_str(value)
|
||||
return None
|
||||
|
||||
|
||||
def _normalize_plan_date(plan: PlPlan) -> date:
|
||||
plan_date = plan.plan_date
|
||||
if isinstance(plan_date, datetime):
|
||||
return plan_date.date()
|
||||
if isinstance(plan_date, date):
|
||||
return plan_date
|
||||
if isinstance(plan_date, str):
|
||||
parsed = parse_date_str(plan_date)
|
||||
return parsed or date.today()
|
||||
return date.today()
|
||||
|
||||
|
||||
def _resolve_dibang_date(plan: PlPlan) -> date:
|
||||
"""入库单用送达时间,出库单用取货时间;缺失时回退计划日期。"""
|
||||
if plan.plan_type == "inbound":
|
||||
resolved = _normalize_to_date(plan.delivery_time)
|
||||
elif plan.plan_type == "outbound":
|
||||
resolved = _normalize_to_date(plan.pickup_time)
|
||||
else:
|
||||
resolved = None
|
||||
return resolved or _normalize_plan_date(plan)
|
||||
|
||||
|
||||
def _month_range(plan_date: date) -> tuple[date, date]:
|
||||
month_start = plan_date.replace(day=1)
|
||||
last_day = calendar.monthrange(plan_date.year, plan_date.month)[1]
|
||||
@@ -68,6 +103,54 @@ def _product_quantity(product: PlPlanProduct) -> float:
|
||||
return 0.0
|
||||
|
||||
|
||||
def _apply_plan_quantity_delta(
|
||||
db_plan: DbPurchasePlan,
|
||||
delta: float,
|
||||
*,
|
||||
user_id: Optional[str],
|
||||
) -> None:
|
||||
current = float(db_plan.planned_quantity or 0)
|
||||
db_plan.planned_quantity = max(0.0, current + float(delta))
|
||||
db_plan.update_by = user_id
|
||||
|
||||
|
||||
def _resolve_link_contributed_qty(
|
||||
link: PlTransportDbLink,
|
||||
inventory: Optional[DbInboundOutbound],
|
||||
) -> float:
|
||||
if link.contributed_qty is not None:
|
||||
return float(link.contributed_qty)
|
||||
if inventory and inventory.quantity is not None:
|
||||
return float(inventory.quantity)
|
||||
return 0.0
|
||||
|
||||
|
||||
def _subtract_plan_contribution(
|
||||
db: Session,
|
||||
*,
|
||||
tenant_no: str,
|
||||
link: PlTransportDbLink,
|
||||
inventory: Optional[DbInboundOutbound],
|
||||
user_id: Optional[str],
|
||||
) -> None:
|
||||
if not link.db_purchase_plan_id:
|
||||
return
|
||||
db_plan = (
|
||||
db.query(DbPurchasePlan)
|
||||
.filter(
|
||||
DbPurchasePlan.id == link.db_purchase_plan_id,
|
||||
DbPurchasePlan.tenant_no == tenant_no,
|
||||
DbPurchasePlan.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not db_plan:
|
||||
return
|
||||
qty = _resolve_link_contributed_qty(link, inventory)
|
||||
if qty:
|
||||
_apply_plan_quantity_delta(db_plan, -qty, user_id=user_id)
|
||||
|
||||
|
||||
def _generate_batch_no(db: Session, tenant_no: str) -> str:
|
||||
rows = (
|
||||
db.query(DbInboundOutbound.batch_no)
|
||||
@@ -124,21 +207,23 @@ def _create_db_plan(
|
||||
product: PlPlanProduct,
|
||||
business_type: str,
|
||||
customer_name: str,
|
||||
dibang_date: date,
|
||||
month_start: date,
|
||||
month_end: date,
|
||||
user_id: Optional[str],
|
||||
dept_id: str,
|
||||
contributed_qty: float,
|
||||
) -> DbPurchasePlan:
|
||||
snapshot = resolve_crm_product(db, tenant_no, product.product_id)
|
||||
db_plan = DbPurchasePlan(
|
||||
id=generate_uuid(),
|
||||
tenant_no=tenant_no,
|
||||
plan_no=generate_plan_no(),
|
||||
plan_date=plan.plan_date,
|
||||
plan_date=dibang_date,
|
||||
start_date=month_start,
|
||||
end_date=month_end,
|
||||
business_type=business_type,
|
||||
planned_quantity=Decimal(str(_product_quantity(product))),
|
||||
planned_quantity=contributed_qty,
|
||||
vehicle_no=(plan.license_plate or "").strip(),
|
||||
customer_name=customer_name or None,
|
||||
remark=f"来源运输计划 {plan.plan_no}",
|
||||
@@ -167,6 +252,7 @@ def _create_db_inventory(
|
||||
business_type: str,
|
||||
customer_name: str,
|
||||
license_plate: str,
|
||||
dibang_date: date,
|
||||
user_id: Optional[str],
|
||||
dept_id: str,
|
||||
) -> DbInboundOutbound:
|
||||
@@ -178,7 +264,7 @@ def _create_db_inventory(
|
||||
inventory = DbInboundOutbound(
|
||||
id=next_inbound_outbound_id(db, tenant_no),
|
||||
tenant_no=tenant_no,
|
||||
date=plan.plan_date,
|
||||
date=dibang_date,
|
||||
partner_company=customer_name,
|
||||
business_type=business_type,
|
||||
quantity=Decimal(str(_product_quantity(product))),
|
||||
@@ -280,6 +366,13 @@ def revoke_regeneratable_dibang_for_transport(
|
||||
.first()
|
||||
)
|
||||
if inventory:
|
||||
_subtract_plan_contribution(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
link=link,
|
||||
inventory=inventory,
|
||||
user_id=user_id,
|
||||
)
|
||||
inventory.del_flag = 1
|
||||
inventory.update_by = user_id
|
||||
link.del_flag = 1
|
||||
@@ -306,13 +399,8 @@ def _sync_transport_inventory_links(
|
||||
if not partner:
|
||||
raise ValueError("无法解析往来单位,请检查合同供方/需方信息")
|
||||
|
||||
plan_date = plan.plan_date
|
||||
if isinstance(plan_date, datetime):
|
||||
plan_date = plan_date.date()
|
||||
elif isinstance(plan_date, str):
|
||||
parsed = parse_date_str(plan_date)
|
||||
plan_date = parsed or date.today()
|
||||
month_start, month_end = _month_range(plan_date)
|
||||
dibang_date = _resolve_dibang_date(plan)
|
||||
month_start, month_end = _month_range(dibang_date)
|
||||
|
||||
license_plate = (transport.license_plate or plan.license_plate or "").strip()
|
||||
|
||||
@@ -322,10 +410,12 @@ def _sync_transport_inventory_links(
|
||||
if not product.product_id:
|
||||
raise ValueError(f"产品【{product.product_name}】未关联 CRM 产品,无法生成地磅出入库单")
|
||||
|
||||
contributed_qty = _product_quantity(product)
|
||||
|
||||
db_plan = find_monthly_db_plan(
|
||||
db,
|
||||
tenant_no,
|
||||
plan_date,
|
||||
dibang_date,
|
||||
business_type,
|
||||
partner,
|
||||
product.product_id,
|
||||
@@ -339,11 +429,15 @@ def _sync_transport_inventory_links(
|
||||
product,
|
||||
business_type,
|
||||
partner,
|
||||
dibang_date,
|
||||
month_start,
|
||||
month_end,
|
||||
user_id,
|
||||
dept_id,
|
||||
contributed_qty,
|
||||
)
|
||||
else:
|
||||
_apply_plan_quantity_delta(db_plan, contributed_qty, user_id=user_id)
|
||||
|
||||
inventory = _create_db_inventory(
|
||||
db,
|
||||
@@ -354,6 +448,7 @@ def _sync_transport_inventory_links(
|
||||
business_type,
|
||||
partner,
|
||||
license_plate,
|
||||
dibang_date,
|
||||
user_id,
|
||||
dept_id,
|
||||
)
|
||||
@@ -366,6 +461,7 @@ def _sync_transport_inventory_links(
|
||||
plan_product_id=product.id,
|
||||
db_purchase_plan_id=db_plan.id if db_plan else "",
|
||||
db_inventory_id=inventory.id,
|
||||
contributed_qty=Decimal(str(contributed_qty)),
|
||||
remark=f"运输计划 {plan.plan_no}",
|
||||
create_by=user_id,
|
||||
create_dept_id=dept_id,
|
||||
|
||||
@@ -319,6 +319,78 @@ def clear_hazard_cert_ready(plan: PlPlan) -> None:
|
||||
plan.hazard_cert_ready_time = None
|
||||
|
||||
|
||||
def _required_hazard_cert_fields(is_toxic: int, is_explosive: int) -> list[str]:
|
||||
fields: list[str] = []
|
||||
if is_toxic:
|
||||
fields.extend(["purchase_cert_url", "transport_cert_url"])
|
||||
if is_explosive:
|
||||
fields.append("legal_use_cert_url")
|
||||
return fields
|
||||
|
||||
|
||||
def _is_hazard_product_cert_complete(
|
||||
product: PlPlanProduct,
|
||||
*,
|
||||
is_toxic: int,
|
||||
is_explosive: int,
|
||||
) -> bool:
|
||||
for field in _required_hazard_cert_fields(is_toxic, is_explosive):
|
||||
if not str(getattr(product, field, "") or "").strip():
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def all_hazard_products_cert_complete(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
products: list[PlPlanProduct],
|
||||
) -> bool:
|
||||
"""计划内所有危化品产品的所需证书槽位均已上传。"""
|
||||
if not products:
|
||||
return True
|
||||
hazard_map = fetch_hazard_flags_map(
|
||||
db,
|
||||
tenant_no,
|
||||
[product.product_id for product in products if product.product_id],
|
||||
)
|
||||
has_hazard = False
|
||||
for product in products:
|
||||
is_toxic, is_explosive = resolve_hazard_flags_from_crm(
|
||||
db,
|
||||
tenant_no,
|
||||
product.product_id,
|
||||
hazard_map=hazard_map,
|
||||
)
|
||||
if not (is_toxic or is_explosive):
|
||||
continue
|
||||
has_hazard = True
|
||||
if not _is_hazard_product_cert_complete(
|
||||
product,
|
||||
is_toxic=is_toxic,
|
||||
is_explosive=is_explosive,
|
||||
):
|
||||
return False
|
||||
return has_hazard
|
||||
|
||||
|
||||
def maybe_auto_set_hazard_cert_ready(
|
||||
db: Session,
|
||||
*,
|
||||
tenant_no: str,
|
||||
plan: PlPlan,
|
||||
products: list[PlPlanProduct],
|
||||
user_id: str,
|
||||
) -> None:
|
||||
"""全部危化品证书已上传时,自动标记证书齐备(与人工确认等效)。"""
|
||||
if int(plan.hazard_cert_ready or 0):
|
||||
return
|
||||
if not all_hazard_products_cert_complete(db, tenant_no, products):
|
||||
return
|
||||
plan.hazard_cert_ready = 1
|
||||
plan.hazard_cert_ready_by = user_id
|
||||
plan.hazard_cert_ready_time = datetime.now()
|
||||
|
||||
|
||||
def validate_hazard_cert_ready_for_transport(
|
||||
db: Session,
|
||||
tenant_no: str,
|
||||
@@ -327,11 +399,14 @@ def validate_hazard_cert_ready_for_transport(
|
||||
) -> None:
|
||||
if not plan_has_hazard_products(db, tenant_no, products):
|
||||
return
|
||||
if not int(plan.hazard_cert_ready or 0):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="该计划危化品证书尚未确认齐备,请至危化品证上传页操作",
|
||||
)
|
||||
if int(plan.hazard_cert_ready or 0):
|
||||
return
|
||||
if all_hazard_products_cert_complete(db, tenant_no, products):
|
||||
return
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="该计划危化品证书尚未确认齐备,请至危化品证上传页操作",
|
||||
)
|
||||
|
||||
|
||||
def set_hazard_cert_ready(
|
||||
@@ -446,6 +521,13 @@ def update_plan_product_certs(
|
||||
row.update_by = user_id
|
||||
updated.append(row)
|
||||
|
||||
maybe_auto_set_hazard_cert_ready(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
plan=plan,
|
||||
products=existing_rows,
|
||||
user_id=user_id,
|
||||
)
|
||||
plan.update_by = user_id
|
||||
db.commit()
|
||||
for row in updated:
|
||||
|
||||
@@ -39,6 +39,17 @@ def _role_rows_for_user(db: Session, user_id: str, tenant_no: str):
|
||||
)
|
||||
|
||||
|
||||
def user_has_bound_roles(db: Session, user_id: str, tenant_no: Optional[str] = None) -> bool:
|
||||
"""用户是否在 sys_user_role 中绑定了至少一个有效角色。"""
|
||||
if not user_id:
|
||||
return False
|
||||
tenant = (tenant_no or "").strip() or settings.DEFAULT_TENANT_NO
|
||||
rows = _role_rows_for_user(db, user_id, tenant)
|
||||
if not rows and tenant != settings.DEFAULT_TENANT_NO:
|
||||
rows = _role_rows_for_user(db, user_id, settings.DEFAULT_TENANT_NO)
|
||||
return bool(rows)
|
||||
|
||||
|
||||
def resolve_web_role_for_user(db: Session, user_id: str, tenant_no: Optional[str] = None) -> str:
|
||||
"""根据用户关联角色解析 Web 端 role(admin / super_admin)。"""
|
||||
if not user_id:
|
||||
@@ -49,17 +60,22 @@ def resolve_web_role_for_user(db: Session, user_id: str, tenant_no: Optional[str
|
||||
rows = _role_rows_for_user(db, user_id, settings.DEFAULT_TENANT_NO)
|
||||
names = [str(r[0]).strip() for r in rows if r and r[0]]
|
||||
if not names:
|
||||
return settings.WEB_AUTH_DEFAULT_ROLE
|
||||
# 未绑定角色:视为普通 admin,无菜单权限(不可默认 super_admin 全菜单)
|
||||
return "admin"
|
||||
mapped = [_map_role_name_to_web(n) for n in names]
|
||||
if "super_admin" in mapped:
|
||||
return "super_admin"
|
||||
return "admin"
|
||||
|
||||
|
||||
def iter_user_menu_perm_codes(db: Session, user_id: str) -> Set[str]:
|
||||
def iter_user_menu_perm_codes(
|
||||
db: Session, user_id: str, tenant_no: Optional[str] = None
|
||||
) -> Set[str]:
|
||||
"""用户通过角色关联得到的菜单权限标识(sys_menu.perms,空则退回菜单 id)。"""
|
||||
if not user_id:
|
||||
return set()
|
||||
if not user_has_bound_roles(db, user_id, tenant_no):
|
||||
return set()
|
||||
tenant = settings.DEFAULT_TENANT_NO
|
||||
try:
|
||||
q = (
|
||||
|
||||
@@ -122,7 +122,6 @@
|
||||
"120": "Community 120",
|
||||
"121": "Community 121",
|
||||
"122": "Community 122",
|
||||
"123": "Community 123",
|
||||
"124": "Community 124",
|
||||
"125": "Community 125",
|
||||
"126": "Community 126",
|
||||
@@ -149,5 +148,16 @@
|
||||
"147": "Community 147",
|
||||
"148": "Community 148",
|
||||
"149": "Community 149",
|
||||
"150": "Community 150"
|
||||
"151": "Community 151",
|
||||
"152": "Community 152",
|
||||
"153": "Community 153",
|
||||
"154": "Community 154",
|
||||
"155": "Community 155",
|
||||
"156": "Community 156",
|
||||
"157": "Community 157",
|
||||
"158": "Community 158",
|
||||
"159": "Community 159",
|
||||
"160": "Community 160",
|
||||
"162": "Community 162",
|
||||
"169": "Community 169"
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
# Graph Report - backend (2026-07-11)
|
||||
# Graph Report - backend (2026-07-12)
|
||||
|
||||
## Corpus Check
|
||||
- 244 files · ~171,565 words
|
||||
- 245 files · ~172,306 words
|
||||
- Verdict: corpus is large enough that graph structure adds value.
|
||||
|
||||
## Summary
|
||||
- 5357 nodes · 38453 edges · 151 communities (148 shown, 3 thin omitted)
|
||||
- Extraction: 51% EXTRACTED · 49% INFERRED · 0% AMBIGUOUS · INFERRED: 18810 edges (avg confidence: 0.5)
|
||||
- 5436 nodes · 38642 edges · 161 communities (159 shown, 2 thin omitted)
|
||||
- Extraction: 51% EXTRACTED · 49% INFERRED · 0% AMBIGUOUS · INFERRED: 18828 edges (avg confidence: 0.5)
|
||||
- Token cost: 0 input · 0 output
|
||||
|
||||
## Graph Freshness
|
||||
- Built from commit: `843e13bc`
|
||||
- Built from commit: `7b1d4c87`
|
||||
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
|
||||
- Run `graphify update .` after code changes (no API cost).
|
||||
|
||||
@@ -138,7 +138,6 @@
|
||||
- [[_COMMUNITY_Community 120|Community 120]]
|
||||
- [[_COMMUNITY_Community 121|Community 121]]
|
||||
- [[_COMMUNITY_Community 122|Community 122]]
|
||||
- [[_COMMUNITY_Community 123|Community 123]]
|
||||
- [[_COMMUNITY_Community 124|Community 124]]
|
||||
- [[_COMMUNITY_Community 125|Community 125]]
|
||||
- [[_COMMUNITY_Community 126|Community 126]]
|
||||
@@ -155,7 +154,18 @@
|
||||
- [[_COMMUNITY_Community 138|Community 138]]
|
||||
- [[_COMMUNITY_Community 148|Community 148]]
|
||||
- [[_COMMUNITY_Community 149|Community 149]]
|
||||
- [[_COMMUNITY_Community 150|Community 150]]
|
||||
- [[_COMMUNITY_Community 151|Community 151]]
|
||||
- [[_COMMUNITY_Community 152|Community 152]]
|
||||
- [[_COMMUNITY_Community 153|Community 153]]
|
||||
- [[_COMMUNITY_Community 154|Community 154]]
|
||||
- [[_COMMUNITY_Community 155|Community 155]]
|
||||
- [[_COMMUNITY_Community 156|Community 156]]
|
||||
- [[_COMMUNITY_Community 157|Community 157]]
|
||||
- [[_COMMUNITY_Community 158|Community 158]]
|
||||
- [[_COMMUNITY_Community 159|Community 159]]
|
||||
- [[_COMMUNITY_Community 160|Community 160]]
|
||||
- [[_COMMUNITY_Community 162|Community 162]]
|
||||
- [[_COMMUNITY_Community 169|Community 169]]
|
||||
|
||||
## God Nodes (most connected - your core abstractions)
|
||||
1. `Session` - 1521 edges
|
||||
@@ -181,110 +191,110 @@
|
||||
- `Any` --uses--> `OperLogMeta` [INFERRED]
|
||||
scripts/migrate_dibang_data.py → app/core/oper_log_context.py
|
||||
|
||||
## Communities (151 total, 3 thin omitted)
|
||||
## Communities (161 total, 2 thin omitted)
|
||||
|
||||
### Community 0 - "Community 0"
|
||||
Cohesion: 0.10
|
||||
Nodes (107): date, DbUserContext, int, Session, str, VehicleRechargeApplyCreate, VehicleRechargeApplyRead, VehicleRechargeApplyUpdate (+99 more)
|
||||
Nodes (112): date, DbUserContext, int, Session, str, VehicleMaintenanceApplyCreate, VehicleMaintenanceApplyRead, VehicleMaintenanceApplyReminder (+104 more)
|
||||
|
||||
### Community 1 - "Community 1"
|
||||
Cohesion: 0.35
|
||||
Nodes (219): Any, bool, str, UploadFile, CRM 线索管理 Pydantic Schemas, CRM 客户管理 Pydantic Schemas, CRM 商机管理 Pydantic Schemas, CRM 客户公海池规则 Pydantic Schemas (+211 more)
|
||||
|
||||
### Community 2 - "Community 2"
|
||||
Cohesion: 0.19
|
||||
Nodes (39): ProcessDefCreateBody, ProcessDefUpdateBody, PublishVersionBody, StartInstanceBody, Any, bool, WfProcessInstance, bool (+31 more)
|
||||
Cohesion: 0.22
|
||||
Nodes (29): Any, int, Session, str, create_instance(), create_process_def(), create_task(), get_all_tasks_for_business() (+21 more)
|
||||
|
||||
### Community 3 - "Community 3"
|
||||
Cohesion: 0.07
|
||||
Nodes (168): _apply_default_invoice_info_to_customers(), category_add(), category_delete(), category_tree(), category_update(), clue_assign(), clue_check_convert(), clue_check_duplicate() (+160 more)
|
||||
Nodes (58): CRM 合同 API 路由 — GET/PUT/DELETE/POST 对齐原 Java CRM, str, CRM 合同 Pydantic Schemas — 对照 CrmContractBo / CrmContractVo, ContractApprovalActionBody, ContractApprovalApproveBody, ContractApprovalByContractBody, ContractApprovalExportBody, ContractApprovalListBody (+50 more)
|
||||
|
||||
### Community 4 - "Community 4"
|
||||
Cohesion: 0.07
|
||||
Nodes (113): bool, DbUserContext, int, 运输计划管理 - 基础数据 API(承运商、车辆、人员), PlCarrier, PlVehicle, Session, str (+105 more)
|
||||
Nodes (112): bool, DbUserContext, int, 运输计划管理 - 基础数据 API(承运商、车辆、人员), PlCarrier, PlVehicle, Session, str (+104 more)
|
||||
|
||||
### Community 5 - "Community 5"
|
||||
Cohesion: 0.22
|
||||
Nodes (28): register_quote_routes(), bool, int, Session, str, accept_quote(), _ensure_quote_in_scope(), generate_pdf() (+20 more)
|
||||
Cohesion: 0.21
|
||||
Nodes (36): register_quote_routes(), bool, CRM 报价单 CRUD — 对照 CrmQuoteServiceImpl.java(P2 全量), Session, str, accept_quote(), create_quote(), _enrich_contact_fields() (+28 more)
|
||||
|
||||
### Community 6 - "Community 6"
|
||||
Cohesion: 0.08
|
||||
Nodes (91): Any, Any, CRM 合同 CRUD — 对照 CrmContractServiceImpl.java(列表/详情 P1), CrmContract, CrmContractProductPriceTier, date, datetime, Decimal (+83 more)
|
||||
Cohesion: 0.05
|
||||
Nodes (78): 运输计划管理 - 基础数据 Schema(承运商、车辆、人员、证书), Session, str, TransportInventoryLinkRead, str, TransportSiteLinkRead, CarrierAuditReject, CarrierBase (+70 more)
|
||||
|
||||
### Community 7 - "Community 7"
|
||||
Cohesion: 0.08
|
||||
Nodes (114): bool, DbUserContext, float, int, PlPlan, PlTransport, Session, str (+106 more)
|
||||
Cohesion: 0.06
|
||||
Nodes (63): int, PlPlan, PlTransport, Session, str, UploadFile, Any, PlPlanTransport (+55 more)
|
||||
|
||||
### Community 8 - "Community 8"
|
||||
Cohesion: 0.04
|
||||
Nodes (115): Any, bool, CRM 产品模块 CRUD(产品分类 + 产品 + 附件), int, Session, str, SysOss, int (+107 more)
|
||||
Nodes (118): Any, bool, CRM 产品模块 CRUD(产品分类 + 产品 + 附件), int, Session, str, SysOss, int (+110 more)
|
||||
|
||||
### Community 9 - "Community 9"
|
||||
Cohesion: 0.25
|
||||
Nodes (19): DbUserContext, Session, str, Session, str, int, Session, str (+11 more)
|
||||
Cohesion: 0.07
|
||||
Nodes (72): CertRead, date, int, Session, str, int, Session, str (+64 more)
|
||||
|
||||
### Community 10 - "Community 10"
|
||||
Cohesion: 0.06
|
||||
Nodes (122): bool, Decimal, int, PlanCreateFull, PlanDriverFeedbackUpdateBody, PlanSiteFeedbackUpdateBody, PlPlan, PlPlanProduct (+114 more)
|
||||
Cohesion: 0.05
|
||||
Nodes (126): bool, Decimal, int, PlPlan, PlPlanProduct, PlTransport, PlVehicle, Session (+118 more)
|
||||
|
||||
### Community 11 - "Community 11"
|
||||
Cohesion: 0.11
|
||||
Nodes (104): date, DbUserContext, int, Session, str, VehicleUreaApplyCreate, VehicleUreaApplyRead, VehicleUreaApplyUpdate (+96 more)
|
||||
Nodes (98): date, DbUserContext, int, Session, str, VehicleUreaApplyCreate, VehicleUreaApplyRead, VehicleUreaApplyUpdate (+90 more)
|
||||
|
||||
### Community 12 - "Community 12"
|
||||
Cohesion: 0.08
|
||||
Nodes (96): attachment_download_url(), attachment_preview_url(), AttachmentDownloadUrlBody, create_dept(), create_dict_data(), create_dict_type(), create_menu(), create_post() (+88 more)
|
||||
Cohesion: 0.06
|
||||
Nodes (169): _apply_default_invoice_info_to_customers(), category_add(), category_delete(), category_tree(), category_update(), clue_assign(), clue_check_convert(), clue_check_duplicate() (+161 more)
|
||||
|
||||
### Community 13 - "Community 13"
|
||||
Cohesion: 0.05
|
||||
Nodes (44): get_plan_arrange_info(), patch_plan_product_certs(), 更新计划危化品产品购买证/运输证(不触发安排运输), 更新计划危化品产品购买证/运输证(不触发安排运输), 更新计划危化品产品购买证/运输证(不触发安排运输), 更新计划危化品产品购买证/运输证(不触发安排运输), 更新计划危化品产品购买证/运输证(不触发安排运输), 更新计划危化品产品购买证/运输证(不触发安排运输) (+36 more)
|
||||
|
||||
### Community 14 - "Community 14"
|
||||
Cohesion: 0.07
|
||||
Nodes (46): Exception, bool, bytes, int, Path, str, str, str (+38 more)
|
||||
Cohesion: 0.05
|
||||
Nodes (74): BxUserContext, Session, str, UploadFile, Any, bool, bytes, int (+66 more)
|
||||
|
||||
### Community 15 - "Community 15"
|
||||
Cohesion: 0.16
|
||||
Nodes (89): date, DbUserContext, int, PlanCreateFull, PlanDriverFeedbackUpdateBody, PlanSiteFeedbackUpdateBody, PlPlan, Session (+81 more)
|
||||
Cohesion: 0.11
|
||||
Nodes (126): date, DbUserContext, int, PlanCreateFull, PlanDriverFeedbackUpdateBody, PlanSiteFeedbackUpdateBody, PlPlan, Session (+118 more)
|
||||
|
||||
### Community 16 - "Community 16"
|
||||
Cohesion: 0.04
|
||||
Nodes (69): 分页查询供方列表 — Java: CrmSupplierInfoController.list, 分页查询供方列表 — Java: CrmSupplierInfoController.list, 分页查询供方列表 — Java: CrmSupplierInfoController.list, 分页查询供方列表 — Java: CrmSupplierInfoController.list, 分页查询供方列表 — Java: CrmSupplierInfoController.list, 分页查询供方列表 — Java: CrmSupplierInfoController.list, 分页查询供方列表 — Java: CrmSupplierInfoController.list, 分页查询供方列表 — Java: CrmSupplierInfoController.list (+61 more)
|
||||
Cohesion: 0.03
|
||||
Nodes (67): invoice_info_list(), 按客户ID查询开票信息(与 by-customer 等价), 按客户ID查询开票信息(与 by-customer 等价), 按客户ID查询开票信息(与 by-customer 等价), 按客户ID查询开票信息(与 by-customer 等价), 分页查询供方列表 — Java: CrmSupplierInfoController.list, 分页查询供方列表 — Java: CrmSupplierInfoController.list, 分页查询供方列表 — Java: CrmSupplierInfoController.list (+59 more)
|
||||
|
||||
### Community 17 - "Community 17"
|
||||
Cohesion: 0.09
|
||||
Nodes (59): Any, bool, CrmContract, datetime, int, Query, Session, str (+51 more)
|
||||
Nodes (57): Any, bool, CrmContract, datetime, int, Query, Session, str (+49 more)
|
||||
|
||||
### Community 18 - "Community 18"
|
||||
Cohesion: 0.16
|
||||
Nodes (32): Any, bool, BxUserContext, BxWorkflowNode, float, int, ReimburseApplyOut, Session (+24 more)
|
||||
Nodes (33): Any, bool, BxUserContext, BxWorkflowNode, float, int, ReimburseApplyOut, Session (+25 more)
|
||||
|
||||
### Community 19 - "Community 19"
|
||||
Cohesion: 0.25
|
||||
Nodes (8): Java: validateFrameworkContractForSubmitApproval, Java: validateFrameworkContractForSubmitApproval, Java: validateFrameworkContractForSubmitApproval, Java: validateFrameworkContractForSubmitApproval, Java: validateFrameworkContractForSubmitApproval, Java: validateFrameworkContractForSubmitApproval, Java: validateFrameworkContractForSubmitApproval, validate_framework_contract_for_submit_approval()
|
||||
Cohesion: 0.19
|
||||
Nodes (18): str, Any, bool, Session, str, CRM 模块 Excel 导出列定义(字段与字典类型对齐 Java *Vo @ExcelDictFormat)。, _yes_no(), _build_workbook() (+10 more)
|
||||
|
||||
### Community 20 - "Community 20"
|
||||
Cohesion: 0.07
|
||||
Nodes (97): Any, int, Session, str, Any, bool, CRM 客户 CRUD(客户主表 + 转移/成交/公海池操作), CrmCustomer (+89 more)
|
||||
Cohesion: 0.08
|
||||
Nodes (40): AttachmentUploadResponse, ChangePasswordRequest, Config, LoginRequest, LoginResponse, Pydantic schemas - 用户认证, ResetPasswordRequest, UserCreate (+32 more)
|
||||
|
||||
### Community 21 - "Community 21"
|
||||
Cohesion: 0.11
|
||||
Nodes (57): Any, bool, int, Session, str, Any, bool, CRM 线索 CRUD(线索主表 + 转化/分配/无效/等级操作) (+49 more)
|
||||
|
||||
### Community 22 - "Community 22"
|
||||
Cohesion: 0.06
|
||||
Nodes (106): bool, DbInboundOutbound, int, PublicInventoryFeedback, PublicInventoryPageRead, Session, str, UploadFile (+98 more)
|
||||
Cohesion: 0.19
|
||||
Nodes (37): bool, DbInboundOutbound, int, PublicInventoryFeedback, PublicInventoryPageRead, UploadFile, bool, int (+29 more)
|
||||
|
||||
### Community 23 - "Community 23"
|
||||
Cohesion: 0.33
|
||||
Nodes (25): DbUserContext, DbWeighingSlip, Session, str, WeighingSlipRead, user_info_from_context(), weighing_to_read(), apply_product_snapshot_to_weighing() (+17 more)
|
||||
Cohesion: 0.09
|
||||
Nodes (116): date, DbUserContext, int, Session, str, VehicleRechargeApplyCreate, VehicleRechargeApplyRead, VehicleRechargeApplyUpdate (+108 more)
|
||||
|
||||
### Community 24 - "Community 24"
|
||||
Cohesion: 0.25
|
||||
Nodes (18): BxUserContext, Session, str, bool, BxUserContext, Session, str, create_expense_item() (+10 more)
|
||||
|
||||
### Community 25 - "Community 25"
|
||||
Cohesion: 0.13
|
||||
Cohesion: 0.14
|
||||
Nodes (29): Any, bool, BxUserContext, BxWorkflowNode, float, int, Session, str (+21 more)
|
||||
|
||||
### Community 26 - "Community 26"
|
||||
@@ -292,168 +302,168 @@ Cohesion: 0.18
|
||||
Nodes (50): Any, CRM 商机 CRUD(主表 + 阶段/赢输单/统计/看板/漏斗), CrmOpportunity, date, datetime, Decimal, int, Session (+42 more)
|
||||
|
||||
### Community 27 - "Community 27"
|
||||
Cohesion: 0.12
|
||||
Nodes (55): DbInboundOutbound, DbWeighingSlip, InboundOutboundRead, WeighingSlipRead, bool, datetime, DbWeighingSlip, Session (+47 more)
|
||||
Cohesion: 0.10
|
||||
Nodes (58): bool, date, DbInboundOutbound, DbPurchasePlan, float, PlPlan, PlPlanProduct, PlTransport (+50 more)
|
||||
|
||||
### Community 28 - "Community 28"
|
||||
Cohesion: 0.08
|
||||
Nodes (45): bool, Session, str, Any, int, Session, str, _append_user_scope_conditions() (+37 more)
|
||||
Cohesion: 0.12
|
||||
Nodes (30): Session, str, _append_user_scope_conditions(), apply_data_scope(), _collect_scope_ids(), _get_child_dept_ids(), get_user_dept_id(), 数据权限过滤(V0.3:基于角色 data_scope 的查询级行级过滤) data_scope 值: 1 = 全部数据 2 = 本部门数据 3 (+22 more)
|
||||
|
||||
### Community 29 - "Community 29"
|
||||
Cohesion: 0.11
|
||||
Nodes (49): register_contract_routes(), bool, Session, bool, batch_submit_approval(), _ensure_contract_in_scope(), execute_change(), finish_contract() (+41 more)
|
||||
Cohesion: 0.10
|
||||
Nodes (90): Any, register_contract_routes(), CRM 合同子模块路由 — 变更/模板/审批独立页, register_contract_sub_routes(), Any, bool, CRM 合同 CRUD — 对照 CrmContractServiceImpl.java(列表/详情 P1), Session (+82 more)
|
||||
|
||||
### Community 30 - "Community 30"
|
||||
Cohesion: 0.36
|
||||
Nodes (31): AdvanceApplyCreate, AdvanceApplyOut, AdvanceApplyUpdate, AdvanceApprovalCreate, BxUserContext, int, Session, str (+23 more)
|
||||
Cohesion: 0.16
|
||||
Nodes (51): AdvanceApplyCreate, AdvanceApplyOut, AdvanceApplyUpdate, AdvanceApprovalCreate, BxUserContext, int, Session, str (+43 more)
|
||||
|
||||
### Community 31 - "Community 31"
|
||||
Cohesion: 0.11
|
||||
Nodes (50): 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/shanxia2.0.sql 基线), refresh_token(), bool, Redis, Request, Session, str, _T (+42 more)
|
||||
Cohesion: 0.12
|
||||
Nodes (44): 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/shanxia2.0.sql 基线), refresh_token(), bool, Redis, Request, str, _T, bool (+36 more)
|
||||
|
||||
### Community 32 - "Community 32"
|
||||
Cohesion: 0.05
|
||||
Nodes (110): Any, bool, datetime, Decimal, int, PlTransport, Session, str (+102 more)
|
||||
Cohesion: 0.07
|
||||
Nodes (118): DbUserContext, FreightSettlementDisburseBody, FreightSettlementSubmitApprovalBody, FreightSettlementUpdate, int, Session, str, bool (+110 more)
|
||||
|
||||
### Community 33 - "Community 33"
|
||||
Cohesion: 0.15
|
||||
Nodes (30): Any, bool, int, Session, str, approve_quote(), can_user_approve(), create_approval_records() (+22 more)
|
||||
|
||||
### Community 34 - "Community 34"
|
||||
Cohesion: 0.07
|
||||
Nodes (28): create_plan_full(), 创建运输计划(完整表单:合同产品 + 可选运输安排), 创建运输计划(完整表单:合同产品 + 可选运输安排), 创建运输计划(完整表单:合同产品 + 可选运输安排), 创建运输计划(完整表单:合同产品 + 可选预选车辆,不自动安排运输), 创建运输计划(完整表单:合同产品 + 可选预选车辆,不自动安排运输), 创建运输计划(完整表单:合同产品 + 可选预选车辆,不自动安排运输), 创建运输计划(完整表单:合同产品 + 可选预选车辆,不自动安排运输) (+20 more)
|
||||
Cohesion: 0.04
|
||||
Nodes (48): create_plan_full(), _generate_plan_no(), 创建运输计划(完整表单:合同产品 + 可选运输安排), 创建运输计划(完整表单:合同产品 + 可选运输安排), 创建运输计划(完整表单:合同产品 + 可选运输安排), 创建运输计划(完整表单:合同产品 + 可选预选车辆,不自动安排运输), 创建运输计划(完整表单:合同产品 + 可选预选车辆,不自动安排运输), 创建运输计划(完整表单:合同产品 + 可选预选车辆,不自动安排运输) (+40 more)
|
||||
|
||||
### Community 35 - "Community 35"
|
||||
Cohesion: 0.13
|
||||
Nodes (97): date, DbUserContext, int, Session, str, VehicleMaintenanceApplyCreate, VehicleMaintenanceApplyRead, VehicleMaintenanceApplyReminder (+89 more)
|
||||
Cohesion: 0.21
|
||||
Nodes (60): InboundOutboundRead, bool, PurchasePlanRead, PurchasePlanWithFillLink, DbUserContext, DbWeighingSlip, Session, str (+52 more)
|
||||
|
||||
### Community 36 - "Community 36"
|
||||
Cohesion: 0.15
|
||||
Nodes (16): _avatar_url_field(), _build_user_info_payload(), _jwt_user_payload(), 与 web-admin `userInfo` 字段约定一致。, 与 web-admin `userInfo` 字段约定一致。, Web 端登录:账号为 sys_user.user_no,密码为 sys_user.password(bcrypt)。, Web 端登录:账号为 sys_user.user_no,密码为 sys_user.password(bcrypt)。, Web 端登录:账号为 sys_user.user_no,密码为 sys_user.password(bcrypt)。 (+8 more)
|
||||
Cohesion: 0.40
|
||||
Nodes (5): bool, str, get_password_hash(), 验证密码(哈希非 bcrypt 或损坏时返回 False,避免抛错), verify_password()
|
||||
|
||||
### Community 37 - "Community 37"
|
||||
Cohesion: 0.13
|
||||
Nodes (39): Any, bool, date, int, Session, str, Session, str (+31 more)
|
||||
Nodes (40): Any, bool, date, int, Session, str, Session, str (+32 more)
|
||||
|
||||
### Community 38 - "Community 38"
|
||||
Cohesion: 0.18
|
||||
Nodes (41): AdvanceIssueRequest, BxUserContext, int, ReimburseApplyOut, Session, str, datetime, str (+33 more)
|
||||
Nodes (44): AdvanceIssueRequest, BxUserContext, int, ReimburseApplyOut, Session, str, datetime, str (+36 more)
|
||||
|
||||
### Community 39 - "Community 39"
|
||||
Cohesion: 0.22
|
||||
Nodes (38): DbInboundOutbound, DbUserContext, InboundOutboundRead, Session, str, CreateLinkForPlanPayload, _apply_inventory_product_update(), _apply_inventory_update() (+30 more)
|
||||
Cohesion: 0.14
|
||||
Nodes (40): DbInboundOutbound, DbUserContext, Session, str, Session, str, user_info_from_context(), _apply_inventory_product_update() (+32 more)
|
||||
|
||||
### Community 40 - "Community 40"
|
||||
Cohesion: 0.02
|
||||
Nodes (197): CRM 报价单 API 路由 — 挂接到 crm.router, CRM 报价单 Pydantic Schemas, Decimal, Decimal, BaseModel, AdvanceApplyCreate, AdvanceApplyOut, AdvanceApplyUpdate (+189 more)
|
||||
Cohesion: 0.15
|
||||
Nodes (33): CRM 报价单 API 路由 — 挂接到 crm.router, CRM 报价单 Pydantic Schemas, BaseModel, QuoteAcceptBody, QuoteApprovalApproveBody, QuoteApprovalListBody, QuoteApprovalRefreshNamesBody, QuoteApprovalWithdrawBody (+25 more)
|
||||
|
||||
### Community 41 - "Community 41"
|
||||
Cohesion: 0.07
|
||||
Nodes (39): _db_pool_keepalive_loop(), _error_json(), lifespan(), _mask_secret(), Exception, int, Request, str (+31 more)
|
||||
Nodes (42): _db_pool_keepalive_loop(), _error_json(), lifespan(), _mask_secret(), Exception, int, Request, str (+34 more)
|
||||
|
||||
### Community 42 - "Community 42"
|
||||
Cohesion: 0.35
|
||||
Nodes (31): BxUserContext, int, Session, str, PagedData, approve_reimburse(), approved_by_me(), _build_details_data() (+23 more)
|
||||
Nodes (30): BxUserContext, int, Session, str, approve_reimburse(), approved_by_me(), _build_details_data(), create_reimburse() (+22 more)
|
||||
|
||||
### Community 43 - "Community 43"
|
||||
Cohesion: 0.13
|
||||
Nodes (37): Any, bool, Decimal, int, Session, str, batch_insert_quote_products(), calculate_subtotal() (+29 more)
|
||||
|
||||
### Community 44 - "Community 44"
|
||||
Cohesion: 0.09
|
||||
Nodes (94): DbUserContext, FreightSettlementDisburseBody, FreightSettlementSubmitApprovalBody, FreightSettlementUpdate, int, Session, str, FreightSettlementDisburseBody (+86 more)
|
||||
Cohesion: 0.07
|
||||
Nodes (95): Any, int, Session, str, Any, bool, CRM 客户 CRUD(客户主表 + 转移/成交/公海池操作), CrmCustomer (+87 more)
|
||||
|
||||
### Community 45 - "Community 45"
|
||||
Cohesion: 0.13
|
||||
Nodes (15): Any, bool, int, str, get_wechat_pay_client(), get_wechat_pay_v2(), 微信支付工具模块 支持微信支付 V3 API 文档参考: https://pay.weixin.qq.com/docs/v3/apis/index.html, 统一下单接口 (V3) Args: out_trade_no: 商户订单号 total_amo (+7 more)
|
||||
|
||||
### Community 46 - "Community 46"
|
||||
Cohesion: 0.07
|
||||
Nodes (56): CRM 合同 API 路由 — GET/PUT/DELETE/POST 对齐原 Java CRM, CRM 合同子模块路由 — 变更/模板/审批独立页, register_contract_sub_routes(), int, CRM 合同 Pydantic Schemas — 对照 CrmContractBo / CrmContractVo, ContractApprovalActionBody, ContractApprovalApproveBody, ContractApprovalByContractBody (+48 more)
|
||||
Cohesion: 0.13
|
||||
Nodes (30): Any, bool, CRM 供方信息 CRUD — 对照 CrmSupplierInfoServiceImpl.java, int, Session, str, _apply_list_filters(), create_supplier_info() (+22 more)
|
||||
|
||||
### Community 47 - "Community 47"
|
||||
Cohesion: 0.16
|
||||
Nodes (33): Any, bool, int, Session, str, Any, Session, str (+25 more)
|
||||
|
||||
### Community 48 - "Community 48"
|
||||
Cohesion: 0.42
|
||||
Nodes (53): date, DbUserContext, int, Session, str, DocumentReminderRead, DocumentReminderUpdate, InventoryLedgerResponse (+45 more)
|
||||
Cohesion: 0.14
|
||||
Nodes (121): date, DbUserContext, int, Session, str, bool, DbUserContext, float (+113 more)
|
||||
|
||||
### Community 49 - "Community 49"
|
||||
Cohesion: 0.09
|
||||
Nodes (43): 运输计划管理 - 基础数据 Schema(承运商、车辆、人员、证书), CarrierAuditReject, CarrierBase, CarrierCreate, CarrierListResponse, CarrierQualificationBase, CarrierRead, CarrierUpdate (+35 more)
|
||||
Cohesion: 0.24
|
||||
Nodes (20): Any, bool, str, _actor_ids(), _actor_names(), assert_row_deletable_by_creator(), assert_rows_deletable_by_creator(), can_delete_by_creator() (+12 more)
|
||||
|
||||
### Community 50 - "Community 50"
|
||||
Cohesion: 0.18
|
||||
Nodes (15): datetime, int, str, datetime, 审批轮次过滤 — 驳回后重新提交时,进度接口仅展示当前轮次。, format_datetime(), generate_id(), generate_random_password() (+7 more)
|
||||
Cohesion: 0.12
|
||||
Nodes (31): datetime, int, str, datetime, date, datetime, str, 审批轮次过滤 — 驳回后重新提交时,进度接口仅展示当前轮次。 (+23 more)
|
||||
|
||||
### Community 51 - "Community 51"
|
||||
Cohesion: 0.16
|
||||
Nodes (33): Any, bool, CrmContractProductPriceTier, date, Decimal, int, Session, str (+25 more)
|
||||
Nodes (31): Any, bool, CrmContractProductPriceTier, date, Decimal, int, Session, str (+23 more)
|
||||
|
||||
### Community 52 - "Community 52"
|
||||
Cohesion: 0.09
|
||||
Nodes (22): batch_assign_owner(), download_contract_pdf(), Java: batchAssignOwner, Java: downloadContractPdf — 返回合同文件 URL/路径, Java: batchAssignOwner, Java: batchAssignOwner, Java: batchAssignOwner, Java: batchAssignOwner (+14 more)
|
||||
Cohesion: 0.08
|
||||
Nodes (25): batch_assign_owner(), download_contract_pdf(), Java: batchAssignOwner, Java: downloadContractPdf — 返回合同文件 URL/路径, Java: batchAssignOwner, Java: batchAssignOwner, Java: batchAssignOwner, Java: batchAssignOwner (+17 more)
|
||||
|
||||
### Community 53 - "Community 53"
|
||||
Cohesion: 0.14
|
||||
Nodes (39): Any, bool, int, Session, str, SysUser, create_user(), get_user() (+31 more)
|
||||
|
||||
### Community 54 - "Community 54"
|
||||
Cohesion: 0.17
|
||||
Nodes (24): Any, bool, bytes, int, Request, str, bool, int (+16 more)
|
||||
Cohesion: 0.23
|
||||
Nodes (10): Any, bool, int, Session, str, camel_to_snake(), CRUDBase, get_model_attr() (+2 more)
|
||||
|
||||
### Community 55 - "Community 55"
|
||||
Cohesion: 0.22
|
||||
Nodes (29): Engine, Path, build_id_maps(), clip_text(), count_rows(), export_mapping(), fetch_rows(), legacy_id_to_str() (+21 more)
|
||||
Cohesion: 0.23
|
||||
Nodes (30): Engine, int, Path, build_id_maps(), clip_text(), count_rows(), export_mapping(), fetch_rows() (+22 more)
|
||||
|
||||
### Community 56 - "Community 56"
|
||||
Cohesion: 0.09
|
||||
Nodes (45): Any, CRM 报价单 CRUD — 对照 CrmQuoteServiceImpl.java(P2 全量), date, datetime, Decimal, Query, _build_quote_filters(), calculate_quote_amount() (+37 more)
|
||||
Cohesion: 0.22
|
||||
Nodes (12): Any, bool, str, WfProcessInstance, _LocalCallbackFn, _dispatch_local(), 工作流回调服务 —— 流程状态变更时通知业务系统, 注册本地回调函数。当 callback_url 以 path 开头时直接调用 fn。 (+4 more)
|
||||
|
||||
### Community 57 - "Community 57"
|
||||
Cohesion: 0.05
|
||||
Nodes (41): PlInventoryTransaction, PlInventoryWarning, 运费结算表 pl_freight_settlement, 库存流水表 pl_inventory_transaction, 运费结算表 pl_freight_settlement, 结算单-运输关联表 pl_freight_settlement_transport, 结算单-运输关联表 pl_freight_settlement_transport, 库存流水表 pl_inventory_transaction (+33 more)
|
||||
Cohesion: 0.46
|
||||
Nodes (13): bool, date, PlCarrier, str, bool, assert_carrier_eligible_for_transport(), carrier_agreement_expired(), carrier_audit_approved() (+5 more)
|
||||
|
||||
### Community 58 - "Community 58"
|
||||
Cohesion: 0.13
|
||||
Nodes (29): API 路由包 — 山峡化工生产一体化门户, create_leave(), delete_leave(), get_leave(), _leave_local_callback(), leave_workflow_callback(), list_leaves(), submit_leave() (+21 more)
|
||||
Cohesion: 0.19
|
||||
Nodes (20): int, str, Any, bool, int, Session, str, business_type_from_path() (+12 more)
|
||||
|
||||
### Community 59 - "Community 59"
|
||||
Cohesion: 0.16
|
||||
Nodes (32): BxUserContext, Session, str, UploadFile, BxUserContext, int, Session, str (+24 more)
|
||||
Cohesion: 0.33
|
||||
Nodes (10): Any, int, Session, str, list_ethnics(), 民族码表 CRUD(全局 sys_ethnic), _row_to_dict(), 民族码表 sys_ethnic(国标 GB/T 3304,全局) (+2 more)
|
||||
|
||||
### Community 60 - "Community 60"
|
||||
Cohesion: 0.03
|
||||
Nodes (72): invoice_info_list(), 按客户ID查询开票信息(与 by-customer 等价), 按客户ID查询开票信息(与 by-customer 等价), 按客户ID查询开票信息(与 by-customer 等价), 按客户ID查询开票信息(与 by-customer 等价), 按客户ID查询开票信息(与 by-customer 等价), 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll, 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll (+64 more)
|
||||
Cohesion: 0.22
|
||||
Nodes (9): Java: withdrawApproval, Java: withdrawApproval, Java: withdrawApproval, Java: withdrawApproval, Java: withdrawApproval, Java: withdrawApproval, Java: withdrawApproval, Java: withdrawApproval (+1 more)
|
||||
|
||||
### Community 61 - "Community 61"
|
||||
Cohesion: 0.27
|
||||
Nodes (24): DbUserContext, float, Session, str, Session, str, DashboardCargoStat, DashboardPartnerCargoStat (+16 more)
|
||||
Cohesion: 0.24
|
||||
Nodes (25): DbUserContext, float, Session, str, Session, str, DashboardCargoStat, DashboardPartnerCargoStat (+17 more)
|
||||
|
||||
### Community 62 - "Community 62"
|
||||
Cohesion: 0.22
|
||||
Nodes (9): export_contracts(), Java: queryList for export, Java: queryList for export, Java: queryList for export, Java: queryList for export, Java: queryList for export, Java: queryList for export, Java: queryList for export (+1 more)
|
||||
Cohesion: 0.10
|
||||
Nodes (21): _build_contract_filters(), export_contracts(), Java: validateContractTypeAndProductsPresence, Java: queryList for export, Java: queryList for export, Java: queryList for export, Java: queryList for export, Java: buildQueryWrapper (+13 more)
|
||||
|
||||
### Community 63 - "Community 63"
|
||||
Cohesion: 0.08
|
||||
Nodes (25): 技术栈, 1. 创建虚拟环境, 项目结构, 1. 创建虚拟环境, 2. 安装依赖, 2. 安装依赖, 3. 配置环境变量, 快速开始 (+17 more)
|
||||
|
||||
### Community 64 - "Community 64"
|
||||
Cohesion: 0.15
|
||||
Nodes (13): ORM → camelCase(单点真相), ORM → camelCase(单点真相), ORM → camelCase(单点真相), ORM → camelCase(单点真相), Java: viewByShareCode, Java: viewByShareCode, Java: viewByShareCode, Java: viewByShareCode (+5 more)
|
||||
Cohesion: 0.05
|
||||
Nodes (46): PlInventoryTransaction, PlInventoryWarning, 计划-运输关联表 pl_plan_transport, 运费结算表 pl_freight_settlement, 库存流水表 pl_inventory_transaction, 运费结算表 pl_freight_settlement, 结算单-运输关联表 pl_freight_settlement_transport, 结算单-运输关联表 pl_freight_settlement_transport (+38 more)
|
||||
|
||||
### Community 65 - "Community 65"
|
||||
Cohesion: 0.22
|
||||
Nodes (29): Any, int, Session, str, create_instance(), create_process_def(), create_task(), get_all_tasks_for_business() (+21 more)
|
||||
Cohesion: 0.28
|
||||
Nodes (8): date, datetime, str, _date_to_str(), _dt_to_str(), PagedQuery, PagedResponse, bx 原系统 API 响应包装(code=0 / msg / data.rows),与门户 success_response 分离。
|
||||
|
||||
### Community 66 - "Community 66"
|
||||
Cohesion: 0.18
|
||||
Nodes (24): bool, date, datetime, int, Session, str, ReimburseApplyOut, allocate_reimburse_title_seq() (+16 more)
|
||||
Cohesion: 0.14
|
||||
Nodes (28): str, T, bool, date, datetime, int, Session, str (+20 more)
|
||||
|
||||
### Community 67 - "Community 67"
|
||||
Cohesion: 0.15
|
||||
Nodes (22): logout(), 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。, 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。, 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。, bool, int, Redis, Request (+14 more)
|
||||
Cohesion: 0.21
|
||||
Nodes (17): bool, int, Redis, Request, str, _claims_from_auth(), _coerce_post_method_override_to_get(), _get_redis_client() (+9 more)
|
||||
|
||||
### Community 68 - "Community 68"
|
||||
Cohesion: 0.25
|
||||
@@ -464,12 +474,12 @@ Cohesion: 0.21
|
||||
Nodes (16): Any, int, str, BusinessException, InvalidStatusTransitionException, log_api_request(), log_delivery_operation(), log_operation() (+8 more)
|
||||
|
||||
### Community 70 - "Community 70"
|
||||
Cohesion: 0.14
|
||||
Nodes (24): bool, BxUserContext, int, Session, str, Any, bool, int (+16 more)
|
||||
Cohesion: 0.11
|
||||
Nodes (32): Any, str, bool, BxUserContext, int, Session, str, Any (+24 more)
|
||||
|
||||
### Community 71 - "Community 71"
|
||||
Cohesion: 0.19
|
||||
Nodes (14): bool, date, Session, str, get_last_month_range(), month_range(), 地磅计划按月自动生成 — 迁移自 simple-dibang-xiaoshou/auto-create-plan/auto_create_monthly_pla, 按月自动生成采购计划。 - 默认仅在每月 1 日执行(force=True 时跳过该限制,供手动触发验收) - 遍历所有租户,按上月计划模板生 (+6 more)
|
||||
Cohesion: 0.16
|
||||
Nodes (16): bool, date, Session, str, get_last_month_range(), month_range(), 地磅计划按月自动生成 — 迁移自 simple-dibang-xiaoshou/auto-create-plan/auto_create_monthly_pla, 按月自动生成采购计划。 - 默认仅在每月 1 日执行(force=True 时跳过该限制,供手动触发验收) - 遍历所有租户,按上月计划模板生 (+8 more)
|
||||
|
||||
### Community 72 - "Community 72"
|
||||
Cohesion: 0.26
|
||||
@@ -480,52 +490,52 @@ Cohesion: 0.32
|
||||
Nodes (22): Any, CrmOpportunity, Decimal, int, Session, str, batch_add_opportunity_products(), _calc_subtotal() (+14 more)
|
||||
|
||||
### Community 74 - "Community 74"
|
||||
Cohesion: 0.18
|
||||
Nodes (11): Java: statisticsTrend, Java: statisticsTrend, Java: statisticsTrend, Java: statisticsTrend, Java: statisticsTrend, Java: statisticsTrend, Java: statisticsTrend, Java: statisticsTrend (+3 more)
|
||||
Cohesion: 0.06
|
||||
Nodes (88): Any, Decimal, Session, str, _advance_after_approval(), _approval_to_dict(), can_user_approve_settlement(), cancel_draft_settlement() (+80 more)
|
||||
|
||||
### Community 75 - "Community 75"
|
||||
Cohesion: 0.15
|
||||
Nodes (22): clue_import_template(), customer_import_template(), product_import_template(), str, Any, bool, Session, str (+14 more)
|
||||
Cohesion: 0.33
|
||||
Nodes (3): 地磅 API 路由聚合 — REST 风格,协议对齐原 simple-dibang-xiaoshou。, 地磅(db)Pydantic schemas — 对齐原 simple-dibang-xiaoshou 契约。, 地磅 API 路由聚合 — REST 风格,协议对齐原 simple-dibang-xiaoshou。
|
||||
|
||||
### Community 76 - "Community 76"
|
||||
Cohesion: 0.23
|
||||
Nodes (25): approve_task(), _assert_wf_instance_in_scope(), _business_type_label(), delete_process_def(), _enrich_task_list(), get_business_all_tasks(), _get_business_form_fields(), get_designer_data() (+17 more)
|
||||
Cohesion: 0.09
|
||||
Nodes (49): API 路由包 — 山峡化工生产一体化门户, create_leave(), delete_leave(), get_leave(), _leave_local_callback(), leave_workflow_callback(), list_leaves(), submit_leave() (+41 more)
|
||||
|
||||
### Community 77 - "Community 77"
|
||||
Cohesion: 0.17
|
||||
Nodes (21): Any, int, str, clear_oper_log_meta(), get_oper_log_meta(), OperLogMeta, 请求级操作日志元数据(ContextVar), 供异步落库线程使用,避免 ContextVar 跨线程丢失。 (+13 more)
|
||||
Cohesion: 0.33
|
||||
Nodes (3): _coerce_id(), OffsetItem, ReimburseDetailCreate
|
||||
|
||||
### Community 78 - "Community 78"
|
||||
Cohesion: 0.23
|
||||
Nodes (10): Any, bool, int, Session, str, camel_to_snake(), CRUDBase, get_model_attr() (+2 more)
|
||||
Cohesion: 0.13
|
||||
Nodes (37): CrmContract, CrmContractProductPriceTier, date, datetime, Decimal, int, Query, contract_row_to_dict() (+29 more)
|
||||
|
||||
### Community 79 - "Community 79"
|
||||
Cohesion: 0.19
|
||||
Nodes (20): int, str, Any, bool, int, Session, str, business_type_from_path() (+12 more)
|
||||
Cohesion: 0.28
|
||||
Nodes (18): Any, int, Session, str, _build_tree(), _cascade_soft_delete(), _collect_with_ancestors(), create_dept() (+10 more)
|
||||
|
||||
### Community 80 - "Community 80"
|
||||
Cohesion: 0.31
|
||||
Nodes (20): Any, bool, CrmOpportunity, datetime, int, Session, str, create_opportunity_follow() (+12 more)
|
||||
|
||||
### Community 81 - "Community 81"
|
||||
Cohesion: 0.10
|
||||
Nodes (21): product_delete(), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids) (+13 more)
|
||||
Cohesion: 0.09
|
||||
Nodes (23): product_delete(), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids), 删除产品(支持单条 body.id 和批量 body.ids) (+15 more)
|
||||
|
||||
### Community 82 - "Community 82"
|
||||
Cohesion: 0.10
|
||||
Nodes (20): 1. 上传到服务器, 1. 上传到服务器, 2. 服务器首次启动, 2. 服务器首次启动, 3. 日常运维, 3. 日常运维, 4. 配置文件, 4. 配置文件 (+12 more)
|
||||
|
||||
### Community 83 - "Community 83"
|
||||
Cohesion: 0.16
|
||||
Nodes (12): float, int, str, 微信消息通知服务 支持微信小程序模板消息推送 使用方式: from app.core.wechat_message import WechatMes, 发送订单状态通知 Args: openid: 用户openid order_n, 发送支付成功通知 Args: openid: 用户openid order_n, 发送配送通知 Args: openid: 配送员openid task_no:, 发送配送时效预警 Args: openid: 配送员openid task_n (+4 more)
|
||||
Cohesion: 0.18
|
||||
Nodes (11): float, int, str, 发送订单状态通知 Args: openid: 用户openid order_n, 发送支付成功通知 Args: openid: 用户openid order_n, 发送配送通知 Args: openid: 配送员openid task_no:, 发送配送时效预警 Args: openid: 配送员openid task_n, 发送退款结果通知 Args: openid: 用户openid order_n (+3 more)
|
||||
|
||||
### Community 84 - "Community 84"
|
||||
Cohesion: 0.34
|
||||
Nodes (19): check_python(), create_venv(), ensure_dirs(), ensure_valid_venv(), ensure_venv_and_runtime(), install_dependencies(), is_running(), log_error() (+11 more)
|
||||
|
||||
### Community 85 - "Community 85"
|
||||
Cohesion: 0.24
|
||||
Nodes (20): Any, bool, str, _actor_ids(), _actor_names(), assert_row_deletable_by_creator(), assert_rows_deletable_by_creator(), can_delete_by_creator() (+12 more)
|
||||
Cohesion: 0.09
|
||||
Nodes (27): Any, datetime, Decimal, _decimal_value(), _parse_optional_datetime(), _period_key(), Java: statisticsOverview, Java: statisticsOverview (+19 more)
|
||||
|
||||
### Community 86 - "Community 86"
|
||||
Cohesion: 0.35
|
||||
@@ -545,31 +555,31 @@ Nodes (17): Any, bool, Session, str, list_share_logs(), _parse_user_agent(), CRM
|
||||
|
||||
### Community 90 - "Community 90"
|
||||
Cohesion: 0.10
|
||||
Nodes (21): Java: createNewVersion — 返回新报价单 id, Java: CrmQuoteServiceImpl.withdrawApproval(校验创建人), Java: CrmQuoteServiceImpl.withdrawApproval(校验创建人), Java: CrmQuoteServiceImpl.withdrawApproval(校验创建人), Java: CrmQuoteServiceImpl.withdrawApproval(校验创建人), Java: CrmQuoteServiceImpl.withdrawApproval(校验创建人), Java: CrmQuoteServiceImpl.withdrawApproval(校验创建人), Java: statisticsOverview (+13 more)
|
||||
Nodes (22): date, Query, _build_quote_filters(), generate_quote_no(), _parse_optional_date(), Java: toContract — 返回 contractId, Java: toContract — 返回 contractId, Java: toContract — 返回 contractId (+14 more)
|
||||
|
||||
### Community 91 - "Community 91"
|
||||
Cohesion: 0.29
|
||||
Nodes (16): Any, int, Session, str, BizLeaveRequest, create_leave(), get_leave(), list_leaves() (+8 more)
|
||||
|
||||
### Community 92 - "Community 92"
|
||||
Cohesion: 0.34
|
||||
Nodes (16): Any, bool, int, Session, str, create_dict_type(), dict_type_code_exists(), get_dict_type() (+8 more)
|
||||
Cohesion: 0.45
|
||||
Nodes (20): DbUserContext, int, Session, str, PlTransportAddress, 运输通用地址库 pl_transport_address, _address_to_read(), _build_address_query() (+12 more)
|
||||
|
||||
### Community 93 - "Community 93"
|
||||
Cohesion: 0.34
|
||||
Nodes (16): Any, bool, int, Session, str, create_tenant(), get_tenant(), list_tenants() (+8 more)
|
||||
|
||||
### Community 94 - "Community 94"
|
||||
Cohesion: 0.28
|
||||
Nodes (13): bool, BxUserContext, BxWorkflowNode, int, Session, str, 按业务类型获取工作流;已知类型不存在时自动创建空定义(无节点)。, 按业务类型获取工作流;已知类型不存在时自动创建空定义(无节点)。 (+5 more)
|
||||
Cohesion: 0.39
|
||||
Nodes (8): bool, BxUserContext, Session, str, 按业务类型获取工作流;已知类型不存在时自动创建空定义(无节点)。, 按业务类型获取工作流;已知类型不存在时自动创建空定义(无节点)。, WorkflowService, BxWorkflowDef
|
||||
|
||||
### Community 95 - "Community 95"
|
||||
Cohesion: 0.28
|
||||
Nodes (13): int, Session, str, _allocate_inbound_outbound_id(), _ensure_sequence_row(), max_numeric_inbound_outbound_id(), next_inbound_outbound_id(), 出入库单 id 生成 — 租户内纯数字自增,并发安全。 (+5 more)
|
||||
Cohesion: 0.23
|
||||
Nodes (14): int, Session, str, _allocate_inbound_outbound_id(), _ensure_sequence_row(), max_numeric_inbound_outbound_id(), next_inbound_outbound_id(), 出入库单 id 生成 — 租户内纯数字自增,并发安全。 (+6 more)
|
||||
|
||||
### Community 96 - "Community 96"
|
||||
Cohesion: 0.06
|
||||
Nodes (66): Any, CertRead, date, int, Session, str, CertRead, Session (+58 more)
|
||||
Cohesion: 0.17
|
||||
Nodes (20): Any, int, str, get_oper_log_meta(), OperLogMeta, 请求级操作日志元数据(ContextVar), 供异步落库线程使用,避免 ContextVar 跨线程丢失。, set_oper_log_meta() (+12 more)
|
||||
|
||||
### Community 97 - "Community 97"
|
||||
Cohesion: 0.38
|
||||
@@ -588,24 +598,24 @@ Cohesion: 0.29
|
||||
Nodes (14): is_valid_dict_type(), load_portal_baseline(), load_sx(), load_zkxw(), main(), map_tag_type(), normalize_row(), parse_insert() (+6 more)
|
||||
|
||||
### Community 101 - "Community 101"
|
||||
Cohesion: 0.29
|
||||
Nodes (10): datetime, int, Session, str, BizNoKind, allocate_next_biz_no(), _format_biz_no(), _prefix_for() (+2 more)
|
||||
Cohesion: 0.22
|
||||
Nodes (17): Session, str, str, 附件上传 bizType 与 MinIO 目录映射(对齐原 CRM oss 上传语义), 优先使用显式 folder;否则按 bizType 映射默认目录。, 优先使用显式 folder;否则按 bizType 映射默认目录。, 优先使用显式 folder;否则按 bizType 映射默认目录。, resolve_upload_folder() (+9 more)
|
||||
|
||||
### Community 102 - "Community 102"
|
||||
Cohesion: 0.27
|
||||
Nodes (11): Any, bool, str, decrypt_login_payload(), ensure_initialized(), extract_login_credentials(), get_public_pem(), 登录凭据 RSA 解密(与前端 JSEncrypt PKCS#1 v1.5 兼容)。 (+3 more)
|
||||
Cohesion: 0.12
|
||||
Nodes (16): int, _fill_contract_linked_count(), list_quotes(), Java: fillContractLinkedCount, Java: fillContractLinkedCount, Java: fillContractLinkedCount, Java: fillContractLinkedCount, Java: fillContractLinkedCount (+8 more)
|
||||
|
||||
### Community 103 - "Community 103"
|
||||
Cohesion: 0.41
|
||||
Nodes (11): Any, str, build_quote_html(), _default_value(), _format_date(), _format_discount(), _format_money(), generate_quote_pdf() (+3 more)
|
||||
|
||||
### Community 104 - "Community 104"
|
||||
Cohesion: 0.22
|
||||
Nodes (12): Any, bool, str, WfProcessInstance, _LocalCallbackFn, _dispatch_local(), 工作流回调服务 —— 流程状态变更时通知业务系统, 注册本地回调函数。当 callback_url 以 path 开头时直接调用 fn。 (+4 more)
|
||||
Cohesion: 0.39
|
||||
Nodes (8): int, LeaveCreate, LeaveUpdate, Config, LeaveCreate, LeaveResponse, LeaveUpdate, Pydantic schemas - 请假申请
|
||||
|
||||
### Community 105 - "Community 105"
|
||||
Cohesion: 0.18
|
||||
Nodes (10): Any, Request, str, str, parse_admin_request_json(), 运营 Web(/web/admin)JSON 请求体解析。 与 ApiCryptoMiddleware 配合:中间件未改写 body 时,在路由内解密 `{, 读取 JSON body;dict 时走 resolve_request_payload(明文 / 密文 / data 包装)。 Returns:, 统一解析接口请求载荷(明文 / 密文信封)。 (+2 more)
|
||||
Cohesion: 0.16
|
||||
Nodes (25): Any, bool, bytes, int, Request, str, bool, int (+17 more)
|
||||
|
||||
### Community 106 - "Community 106"
|
||||
Cohesion: 0.36
|
||||
@@ -616,52 +626,52 @@ Cohesion: 0.60
|
||||
Nodes (10): Any, Session, str, _apply_create_range(), _base_query(), get_clue_level_statistics(), get_clue_overview_statistics(), get_clue_owner_statistics() (+2 more)
|
||||
|
||||
### Community 108 - "Community 108"
|
||||
Cohesion: 0.45
|
||||
Nodes (20): DbUserContext, int, Session, str, PlTransportAddress, 运输通用地址库 pl_transport_address, _address_to_read(), _build_address_query() (+12 more)
|
||||
Cohesion: 0.35
|
||||
Nodes (5): Any, str, WfProcessInstance, WorkflowEngine, WfProcessInstance
|
||||
|
||||
### Community 109 - "Community 109"
|
||||
Cohesion: 0.32
|
||||
Nodes (7): Session, str, apply_product_snapshot_to_inventory(), ProductSnapshot, 地磅模块 — CRM 产品解析与快照写入。, 校验 CRM 产品并返回名称/规格快照。新建单据 product_id 必填。, resolve_crm_product()
|
||||
Cohesion: 0.14
|
||||
Nodes (22): Session, str, bool, Session, str, get_user_menu_codes(), 菜单 code 权限守卫(V0.3:通过 sys_user_role → sys_role_menu → sys_menu.perms), 读取用户授权的菜单权限标识集合(与前端 meta.menuCode / perms 对齐)。 (+14 more)
|
||||
|
||||
### Community 110 - "Community 110"
|
||||
Cohesion: 0.18
|
||||
Nodes (11): generate_contract_no(), Java: generateContractNo — {供方编码}/X-JT + yyyyMMdd + 3位流水, Java: generateContractNo — {供方编码}/X-JT + yyyyMMdd + 3位流水, Java: generateContractNo — {供方编码}/X-JT{yyyyMMdd}{3位序号}, Java: generateContractNo — {供方编码}/X-JT{yyyyMMdd}{3位序号}, Java: generateContractNo — {供方编码}/X-JT{yyyyMMdd}{3位序号}, Java: generateContractNo — {供方编码}/X-JT{yyyyMMdd}{3位序号}, Java: generateContractNo — {供方编码}/X-JT{yyyyMMdd}{3位序号} (+3 more)
|
||||
Cohesion: 0.05
|
||||
Nodes (38): create_contract(), create_from_opportunity(), generate_contract_name(), generate_contract_no(), _normalize_framework_contract_data(), _opportunity_products_to_contract_products(), Java: normalizeFrameworkContractData, Java: createFromOpportunity — 返回合同 id (+30 more)
|
||||
|
||||
### Community 111 - "Community 111"
|
||||
Cohesion: 0.18
|
||||
Nodes (11): generate_contract_name(), Java: generateContractName, Java: generateContractName, Java: generateContractName, Java: generateContractName, Java: generateContractName, Java: generateContractName, Java: generateContractName (+3 more)
|
||||
Cohesion: 0.17
|
||||
Nodes (42): ProcessDefCreateBody, ProcessDefUpdateBody, PublishVersionBody, 审批意见:前端传 commentText,兼容 comment。, 审批意见:前端传 commentText,兼容 comment。, StartInstanceBody, TaskActionBody, Any (+34 more)
|
||||
|
||||
### Community 112 - "Community 112"
|
||||
Cohesion: 0.28
|
||||
Nodes (18): Any, int, Session, str, _build_tree(), _cascade_soft_delete(), _collect_with_ancestors(), create_dept() (+10 more)
|
||||
Cohesion: 0.08
|
||||
Nodes (103): attachment_download_url(), attachment_preview_url(), AttachmentDownloadUrlBody, create_dept(), create_dict_data(), create_dict_type(), create_menu(), create_post() (+95 more)
|
||||
|
||||
### Community 113 - "Community 113"
|
||||
Cohesion: 0.20
|
||||
Nodes (10): create_from_quote(), _quote_products_to_contract_products(), Java: createFromQuote — 返回合同 id, Java: createFromQuote — 返回合同 id, Java: createFromQuote — 返回合同 id, Java: createFromQuote — 返回合同 id, Java: createFromQuote — 返回合同 id, Java: createFromQuote — 返回合同 id (+2 more)
|
||||
Cohesion: 0.10
|
||||
Nodes (20): create_from_quote(), _quote_products_to_contract_products(), Java: createFromQuote — 返回合同 id, Java: renewContract — 基础续签, Java: createFromQuote — 返回合同 id, Java: renewContract — 基础续签, Java: createFromQuote — 返回合同 id, Java: createFromQuote — 返回合同 id (+12 more)
|
||||
|
||||
### Community 115 - "Community 115"
|
||||
Cohesion: 0.29
|
||||
Nodes (9): AST, expr, field_has_alias(), iter_field_assignments(), main(), bool, int, str (+1 more)
|
||||
|
||||
### Community 116 - "Community 116"
|
||||
Cohesion: 0.33
|
||||
Nodes (8): Decimal, str, derive_legacy_status_from_three(), derive_three_status_from_legacy(), 阶段A兼容:从旧 status 推导三字段。 约束:旧 status 本身混合了支付/配送/订单生命周期,因此只能尽力推断; 若业务侧已有更精确, 阶段A兼容:从三字段反推旧 status(用于旧前端/旧筛选参数兼容)。, ThreeStatus, _to_decimal()
|
||||
Cohesion: 0.13
|
||||
Nodes (15): calculate_quote_amount(), create_new_version(), Java: createNewVersion — 返回新报价单 id, Java: createNewVersion — 返回新报价单 id, Java: createNewVersion — 返回新报价单 id, Java: createNewVersion — 返回新报价单 id, Java: createNewVersion — 返回新报价单 id, Java: createNewVersion — 返回新报价单 id (+7 more)
|
||||
|
||||
### Community 117 - "Community 117"
|
||||
Cohesion: 0.50
|
||||
Nodes (3): Any, str, 与门户 JWT / apply_data_scope 所需的 user_info 字段对齐。
|
||||
Cohesion: 0.39
|
||||
Nodes (6): _inject_user_info_after_tenant(), patch_contract_routes(), patch_quote_routes(), patch_route_file(), Path, str
|
||||
|
||||
### Community 118 - "Community 118"
|
||||
Cohesion: 0.83
|
||||
Nodes (3): _add_user_info_kwarg(), patch_file(), str
|
||||
Cohesion: 0.34
|
||||
Nodes (16): Any, bool, int, Session, str, create_dict_type(), dict_type_code_exists(), get_dict_type() (+8 more)
|
||||
|
||||
### Community 119 - "Community 119"
|
||||
Cohesion: 0.12
|
||||
Nodes (17): create_new_version(), Java: toContract — 返回 contractId, Java: createNewVersion — 返回新报价单 id, Java: toContract — 返回 contractId, Java: createNewVersion — 返回新报价单 id, Java: toContract — 返回 contractId, Java: createNewVersion — 返回新报价单 id, Java: createNewVersion — 返回新报价单 id (+9 more)
|
||||
Cohesion: 0.15
|
||||
Nodes (13): ORM → camelCase(单点真相), ORM → camelCase(单点真相), ORM → camelCase(单点真相), ORM → camelCase(单点真相), Java: viewByShareCode, Java: viewByShareCode, Java: viewByShareCode, Java: viewByShareCode (+5 more)
|
||||
|
||||
### Community 120 - "Community 120"
|
||||
Cohesion: 0.22
|
||||
Nodes (9): Java: renewContract — 基础续签, Java: renewContract — 基础续签, Java: renewContract — 基础续签, Java: renewContract — 基础续签, Java: renewContract — 基础续签, Java: renewContract — 基础续签, Java: renewContract — 基础续签, Java: renewContract — 基础续签 (+1 more)
|
||||
Cohesion: 0.27
|
||||
Nodes (11): Any, bool, str, decrypt_login_payload(), ensure_initialized(), extract_login_credentials(), get_public_pem(), 登录凭据 RSA 解密(与前端 JSEncrypt PKCS#1 v1.5 兼容)。 (+3 more)
|
||||
|
||||
### Community 121 - "Community 121"
|
||||
Cohesion: 0.22
|
||||
@@ -671,84 +681,128 @@ Nodes (9): batch_submit_contract_approval(), Java: batchSubmitApproval, Java: ba
|
||||
Cohesion: 0.32
|
||||
Nodes (7): str, eq_unicode(), like_unicode(), MySQL 字符串比较/模糊匹配时统一排序规则,避免 Illegal mix of collations。, 跨表字符串等值比较(JOIN / WHERE)时统一为 utf8mb4_unicode_ci。, 将列显式设为 utf8mb4_unicode_ci。, unicode_ci()
|
||||
|
||||
### Community 123 - "Community 123"
|
||||
Cohesion: 0.10
|
||||
Nodes (20): _generate_plan_no(), 生成计划编号 SXDDHYYYYMMDD######, 生成计划编号 SXDDHYYYYMMDD######, 生成计划编号 SXDDHYYYYMMDD######, 生成计划编号 SXDDHYYYYMMDD######, 生成计划编号 SXDDHYYYYMMDD######, 生成计划编号 SXDDHYYYYMMDD######, 生成计划编号 SXDDHYYYYMMDD###### (+12 more)
|
||||
|
||||
### Community 124 - "Community 124"
|
||||
Cohesion: 0.12
|
||||
Nodes (15): 报销(bx)API 路由聚合 — REST 风格,协议对齐 bx 原系统。, date, datetime, str, 报销(bx)Pydantic 模型 — 响应协议与 bx 原系统保持一致, _date_to_str(), _dt_to_str(), bx 原系统 API 响应包装(code=0 / msg / data.rows),与门户 success_response 分离。 (+7 more)
|
||||
Cohesion: 0.18
|
||||
Nodes (9): 报销(bx)API 路由聚合 — REST 风格,协议对齐 bx 原系统。, 报销(bx)Pydantic 模型 — 响应协议与 bx 原系统保持一致, bx_health(), 报销(bx)API 路由聚合 — REST 风格,协议对齐 bx 原系统。, P0 健康检查:验证 bx 路由已挂载且响应协议为 code=0。, P0 健康检查:验证 bx 路由已挂载且响应协议为 code=0。, P0 健康检查:验证 bx 路由已挂载且响应协议为 code=0。, P0 健康检查:验证 bx 路由已挂载且响应协议为 code=0。 (+1 more)
|
||||
|
||||
### Community 125 - "Community 125"
|
||||
Cohesion: 0.25
|
||||
Nodes (8): generate_template_no(), Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}
|
||||
Cohesion: 0.22
|
||||
Nodes (9): generate_template_no(), Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号}, Java: generateTemplateNo — TMPL{yyyyMMdd}{4位序号} (+1 more)
|
||||
|
||||
### Community 126 - "Community 126"
|
||||
Cohesion: 0.22
|
||||
Nodes (9): create_from_opportunity(), _opportunity_products_to_contract_products(), Java: createFromOpportunity — 返回合同 id, Java: createFromOpportunity — 返回合同 id, Java: createFromOpportunity — 返回合同 id, Java: createFromOpportunity — 返回合同 id, Java: createFromOpportunity — 返回合同 id, Java: createFromOpportunity — 返回合同 id (+1 more)
|
||||
Cohesion: 0.17
|
||||
Nodes (10): FreightSettlementApprovalActionBody, FreightSettlementByTransportRead, FreightSettlementCreate, FreightSettlementDisburseBody, FreightSettlementListItem, FreightSettlementSubmitApprovalBody, SettlementApprovalFlowItem, SettlementPlanSummary (+2 more)
|
||||
|
||||
### Community 127 - "Community 127"
|
||||
Cohesion: 0.12
|
||||
Nodes (67): bool, DbPurchasePlan, DbUserContext, PurchasePlanRead, PurchasePlanWithFillLink, Session, str, bool (+59 more)
|
||||
Cohesion: 0.16
|
||||
Nodes (37): DbPurchasePlan, DbUserContext, Session, str, InboundOutboundRead, _apply_plan_product_update(), _apply_plan_update(), _base_query() (+29 more)
|
||||
|
||||
### Community 128 - "Community 128"
|
||||
Cohesion: 0.35
|
||||
Nodes (5): Any, str, WfProcessInstance, WorkflowEngine, WfProcessInstance
|
||||
Cohesion: 0.24
|
||||
Nodes (8): Decimal, VehicleUreaApplyCreate, VehicleUreaApplyListResponse, VehicleUreaApplyUpdate, VehicleUreaDisburseBoardListResponse, VehicleUreaDisburseCreate, VehicleUreaDisburseListResponse, VehicleUreaDisburseUpdate
|
||||
|
||||
### Community 129 - "Community 129"
|
||||
Cohesion: 0.09
|
||||
Nodes (18): 地磅 API 路由聚合 — REST 风格,协议对齐原 simple-dibang-xiaoshou。, Session, 地磅(db)Pydantic schemas — 对齐原 simple-dibang-xiaoshou 契约。, _auto_fill_create_dept_id(), get_db(), ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID, ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID, ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID (+10 more)
|
||||
Cohesion: 0.18
|
||||
Nodes (11): Java: createNewVersion — 返回新报价单 id, Java: CrmQuoteServiceImpl.withdrawApproval(校验创建人), Java: CrmQuoteServiceImpl.withdrawApproval(校验创建人), Java: CrmQuoteServiceImpl.withdrawApproval(校验创建人), Java: CrmQuoteServiceImpl.withdrawApproval(校验创建人), Java: CrmQuoteServiceImpl.withdrawApproval(校验创建人), Java: CrmQuoteServiceImpl.withdrawApproval(校验创建人), Java: CrmQuoteServiceImpl.withdrawApproval(校验创建人) (+3 more)
|
||||
|
||||
### Community 130 - "Community 130"
|
||||
Cohesion: 0.07
|
||||
Nodes (35): list_roles_for_select(), 返回运营端角色下拉选项(label=库中角色名,value=Web 角色码)。, 返回运营端角色下拉选项(label=库中角色名,value=Web 角色码)。, 返回运营端角色下拉选项(label=库中角色名,value=Web 角色码)。, get_current_user_profile(), get_web_auth_menu_codes(), public_quote_share(), 获取当前用户信息(与 sys_user 字段对应)。 (+27 more)
|
||||
Nodes (34): _avatar_url_field(), _build_user_info_payload(), get_current_user_profile(), get_web_auth_menu_codes(), _jwt_user_payload(), logout(), public_quote_share(), 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。 (+26 more)
|
||||
|
||||
### Community 131 - "Community 131"
|
||||
Cohesion: 0.40
|
||||
Nodes (3): BaseSettings, Config, Settings
|
||||
|
||||
### Community 132 - "Community 132"
|
||||
Cohesion: 0.40
|
||||
Nodes (4): Any, str, 供 get_tenant_no 等门户工具使用。, 与门户 JWT / apply_data_scope 所需的 user_info 字段对齐。
|
||||
Cohesion: 0.18
|
||||
Nodes (11): Java: statisticsStatus, Java: statisticsStatus, Java: statisticsStatus, Java: statisticsStatus, Java: statisticsStatus, Java: statisticsStatus, Java: statisticsStatus, Java: statisticsStatus (+3 more)
|
||||
|
||||
### Community 134 - "Community 134"
|
||||
Cohesion: 0.50
|
||||
Nodes (4): Web 端登录用 RSA 公钥(PKCS#1 v1.5,与 JSEncrypt 一致), Web 端登录用 RSA 公钥(PKCS#1 v1.5,与 JSEncrypt 一致), Web 端登录用 RSA 公钥(PKCS#1 v1.5,与 JSEncrypt 一致), web_login_rsa_public_key()
|
||||
Cohesion: 0.18
|
||||
Nodes (11): 解析司机信息:优先 pl_personnel,兼容旧 pl_driver。, 解析司机信息:优先 pl_personnel,兼容旧 pl_driver。, 解析司机信息:优先 pl_personnel,兼容旧 pl_driver。, 解析司机信息:优先 pl_personnel,兼容旧 pl_driver。, 解析司机信息:优先 pl_personnel,兼容旧 pl_driver。, 解析司机信息:优先 pl_personnel,兼容旧 pl_driver。, 解析司机信息:优先 pl_personnel,兼容旧 pl_driver。, 解析司机信息:优先 pl_personnel,兼容旧 pl_driver。 (+3 more)
|
||||
|
||||
### Community 135 - "Community 135"
|
||||
Cohesion: 0.38
|
||||
Nodes (4): str, _coerce_id(), ExpenseItemCreate, ExpenseItemUpdate
|
||||
Cohesion: 0.20
|
||||
Nodes (10): Java: deleteWithValidByIds — 软删除, Java: deleteWithValidByIds — 软删除, Java: deleteWithValidByIds — 软删除, Java: deleteWithValidByIds — 软删除, Java: deleteWithValidByIds — 软删除, Java: deleteWithValidByIds — 软删除, Java: deleteWithValidByIds, Java: deleteWithValidByIds — 软删除 (+2 more)
|
||||
|
||||
### Community 136 - "Community 136"
|
||||
Cohesion: 0.32
|
||||
Nodes (12): Any, bool, bytes, int, str, _b64url_decode(), _b64url_encode(), decrypt_json() (+4 more)
|
||||
Cohesion: 0.18
|
||||
Nodes (10): Any, Request, str, str, parse_admin_request_json(), 运营 Web(/web/admin)JSON 请求体解析。 与 ApiCryptoMiddleware 配合:中间件未改写 body 时,在路由内解密 `{, 读取 JSON body;dict 时走 resolve_request_payload(明文 / 密文 / data 包装)。 Returns:, 统一解析接口请求载荷(明文 / 密文信封)。 (+2 more)
|
||||
|
||||
### Community 137 - "Community 137"
|
||||
Cohesion: 0.33
|
||||
Nodes (6): _fill_contract_linked_count(), Java: fillContractLinkedCount, Java: fillContractLinkedCount, Java: fillContractLinkedCount, Java: fillContractLinkedCount, Java: fillContractLinkedCount
|
||||
Nodes (8): Decimal, str, derive_legacy_status_from_three(), derive_three_status_from_legacy(), 阶段A兼容:从旧 status 推导三字段。 约束:旧 status 本身混合了支付/配送/订单生命周期,因此只能尽力推断; 若业务侧已有更精确, 阶段A兼容:从三字段反推旧 status(用于旧前端/旧筛选参数兼容)。, ThreeStatus, _to_decimal()
|
||||
|
||||
### Community 148 - "Community 148"
|
||||
Cohesion: 0.39
|
||||
Nodes (6): _inject_user_info_after_tenant(), patch_contract_routes(), patch_quote_routes(), patch_route_file(), Path, str
|
||||
Cohesion: 0.38
|
||||
Nodes (4): str, _coerce_id(), ExpenseItemCreate, ExpenseItemUpdate
|
||||
|
||||
### Community 149 - "Community 149"
|
||||
Cohesion: 0.33
|
||||
Nodes (5): _coerce_optional_id(), WorkflowDefCreate, WorkflowDefUpdate, WorkflowNodeCreate, WorkflowNodeOut
|
||||
|
||||
### Community 151 - "Community 151"
|
||||
Cohesion: 0.03
|
||||
Nodes (60): 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll, 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll, 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll, 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll, 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll, 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll, 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll, 供方全量列表(下拉)— Java: CrmSupplierInfoController.listAll (+52 more)
|
||||
|
||||
### Community 152 - "Community 152"
|
||||
Cohesion: 0.38
|
||||
Nodes (5): TransportAddressBase, TransportAddressCreate, TransportAddressListResponse, TransportAddressRead, TransportAddressUpdate
|
||||
|
||||
### Community 153 - "Community 153"
|
||||
Cohesion: 0.40
|
||||
Nodes (5): Web 端登录用 RSA 公钥(PKCS#1 v1.5,与 JSEncrypt 一致), Web 端登录用 RSA 公钥(PKCS#1 v1.5,与 JSEncrypt 一致), Web 端登录用 RSA 公钥(PKCS#1 v1.5,与 JSEncrypt 一致), Web 端登录用 RSA 公钥(PKCS#1 v1.5,与 JSEncrypt 一致), web_login_rsa_public_key()
|
||||
|
||||
### Community 154 - "Community 154"
|
||||
Cohesion: 0.29
|
||||
Nodes (10): datetime, int, Session, str, BizNoKind, allocate_next_biz_no(), _format_biz_no(), _prefix_for() (+2 more)
|
||||
|
||||
### Community 155 - "Community 155"
|
||||
Cohesion: 0.40
|
||||
Nodes (4): _auto_fill_create_dept_id(), ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID, ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID, ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID
|
||||
|
||||
### Community 156 - "Community 156"
|
||||
Cohesion: 0.40
|
||||
Nodes (4): ContactByCustomerBody, ContactDetailBody, ContactSetPrimaryBody, CRM 联系人 Pydantic Schemas
|
||||
|
||||
### Community 157 - "Community 157"
|
||||
Cohesion: 0.40
|
||||
Nodes (4): CreateLinkForPlanPayload, InboundOutboundBase, InboundOutboundCreate, InboundOutboundUpdate
|
||||
|
||||
### Community 158 - "Community 158"
|
||||
Cohesion: 0.50
|
||||
Nodes (3): Config, PageResult, ResponseModel
|
||||
|
||||
### Community 159 - "Community 159"
|
||||
Cohesion: 0.50
|
||||
Nodes (3): PurchasePlanBase, PurchasePlanCreate, PurchasePlanUpdate
|
||||
|
||||
### Community 160 - "Community 160"
|
||||
Cohesion: 0.50
|
||||
Nodes (3): WeighingSlipBase, WeighingSlipCreate, WeighingSlipUpdate
|
||||
|
||||
### Community 162 - "Community 162"
|
||||
Cohesion: 0.50
|
||||
Nodes (3): Any, str, 与门户 JWT / apply_data_scope 所需的 user_info 字段对齐。
|
||||
|
||||
### Community 169 - "Community 169"
|
||||
Cohesion: 0.18
|
||||
Nodes (11): Java: statisticsStatus, Java: statisticsStatus, Java: statisticsStatus, Java: statisticsStatus, Java: statisticsStatus, Java: statisticsStatus, Java: statisticsStatus, Java: statisticsStatus (+3 more)
|
||||
Nodes (12): Session, Session, get_current_user(), get_current_user_by_token(), 根据 token 获取当前用户(包含 Redis 会话校验;会话滑动续期), 根据 token 获取当前用户(包含 Redis 会话校验;会话滑动续期), 根据 token 获取当前用户(包含 Redis 会话校验;会话滑动续期), 获取当前用户(sync 依赖,配合 sync 路由在线程池执行,避免阻塞事件循环)。 (+4 more)
|
||||
|
||||
## Knowledge Gaps
|
||||
- **77 isolated node(s):** `Session`, `Request`, `bool`, `bool`, `Config` (+72 more)
|
||||
These have ≤1 connection - possible missing edges or undocumented components.
|
||||
- **3 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
|
||||
- **2 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
|
||||
|
||||
## Suggested Questions
|
||||
_Questions this graph is uniquely positioned to answer:_
|
||||
|
||||
- **Why does `Session` connect `Community 3` to `Community 0`, `Community 1`, `Community 2`, `Community 4`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 10`, `Community 11`, `Community 12`, `Community 13`, `Community 15`, `Community 16`, `Community 17`, `Community 18`, `Community 19`, `Community 20`, `Community 21`, `Community 22`, `Community 23`, `Community 24`, `Community 25`, `Community 26`, `Community 27`, `Community 28`, `Community 29`, `Community 30`, `Community 31`, `Community 32`, `Community 33`, `Community 34`, `Community 35`, `Community 36`, `Community 37`, `Community 38`, `Community 39`, `Community 40`, `Community 42`, `Community 43`, `Community 44`, `Community 46`, `Community 47`, `Community 51`, `Community 52`, `Community 53`, `Community 54`, `Community 56`, `Community 58`, `Community 59`, `Community 60`, `Community 61`, `Community 62`, `Community 64`, `Community 65`, `Community 66`, `Community 68`, `Community 70`, `Community 71`, `Community 72`, `Community 73`, `Community 74`, `Community 75`, `Community 76`, `Community 78`, `Community 79`, `Community 80`, `Community 81`, `Community 86`, `Community 87`, `Community 89`, `Community 90`, `Community 91`, `Community 92`, `Community 93`, `Community 94`, `Community 95`, `Community 96`, `Community 97`, `Community 98`, `Community 99`, `Community 101`, `Community 107`, `Community 108`, `Community 109`, `Community 110`, `Community 112`, `Community 113`, `Community 119`, `Community 120`, `Community 121`, `Community 123`, `Community 125`, `Community 126`, `Community 127`, `Community 128`, `Community 129`, `Community 130`, `Community 137`, `Community 149`?**
|
||||
_High betweenness centrality (0.253) - this node is a cross-community bridge._
|
||||
- **Why does `BaseModel` connect `Community 40` to `Community 0`, `Community 1`, `Community 2`, `Community 4`, `Community 135`, `Community 7`, `Community 9`, `Community 10`, `Community 11`, `Community 12`, `Community 14`, `Community 15`, `Community 18`, `Community 22`, `Community 23`, `Community 24`, `Community 30`, `Community 35`, `Community 38`, `Community 39`, `Community 42`, `Community 44`, `Community 46`, `Community 48`, `Community 49`, `Community 58`, `Community 59`, `Community 61`, `Community 66`, `Community 76`, `Community 96`, `Community 114`, `Community 127`?**
|
||||
_High betweenness centrality (0.050) - this node is a cross-community bridge._
|
||||
- **Why does `int` connect `Community 30` to `Community 0`, `Community 1`, `Community 2`, `Community 3`, `Community 4`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 10`, `Community 11`, `Community 12`, `Community 14`, `Community 15`, `Community 16`, `Community 17`, `Community 18`, `Community 20`, `Community 21`, `Community 22`, `Community 24`, `Community 25`, `Community 26`, `Community 27`, `Community 28`, `Community 29`, `Community 32`, `Community 33`, `Community 35`, `Community 37`, `Community 38`, `Community 41`, `Community 42`, `Community 43`, `Community 44`, `Community 45`, `Community 46`, `Community 47`, `Community 50`, `Community 51`, `Community 52`, `Community 53`, `Community 54`, `Community 55`, `Community 58`, `Community 59`, `Community 65`, `Community 66`, `Community 67`, `Community 68`, `Community 69`, `Community 70`, `Community 71`, `Community 72`, `Community 73`, `Community 76`, `Community 77`, `Community 78`, `Community 79`, `Community 80`, `Community 83`, `Community 86`, `Community 91`, `Community 92`, `Community 93`, `Community 94`, `Community 95`, `Community 96`, `Community 97`, `Community 98`, `Community 99`, `Community 101`, `Community 108`, `Community 112`, `Community 121`, `Community 128`, `Community 136`?**
|
||||
_High betweenness centrality (0.046) - this node is a cross-community bridge._
|
||||
- **Why does `Session` connect `Community 112` to `Community 0`, `Community 1`, `Community 2`, `Community 4`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 10`, `Community 11`, `Community 12`, `Community 13`, `Community 14`, `Community 15`, `Community 16`, `Community 17`, `Community 18`, `Community 19`, `Community 21`, `Community 22`, `Community 23`, `Community 24`, `Community 25`, `Community 26`, `Community 27`, `Community 28`, `Community 29`, `Community 30`, `Community 31`, `Community 32`, `Community 33`, `Community 34`, `Community 35`, `Community 37`, `Community 38`, `Community 39`, `Community 42`, `Community 43`, `Community 44`, `Community 46`, `Community 47`, `Community 48`, `Community 51`, `Community 52`, `Community 53`, `Community 54`, `Community 58`, `Community 59`, `Community 60`, `Community 61`, `Community 62`, `Community 66`, `Community 68`, `Community 70`, `Community 71`, `Community 72`, `Community 73`, `Community 74`, `Community 76`, `Community 78`, `Community 79`, `Community 80`, `Community 81`, `Community 85`, `Community 86`, `Community 87`, `Community 89`, `Community 90`, `Community 91`, `Community 92`, `Community 93`, `Community 94`, `Community 95`, `Community 97`, `Community 98`, `Community 99`, `Community 101`, `Community 102`, `Community 104`, `Community 105`, `Community 107`, `Community 108`, `Community 109`, `Community 110`, `Community 111`, `Community 113`, `Community 116`, `Community 118`, `Community 119`, `Community 121`, `Community 125`, `Community 127`, `Community 129`, `Community 130`, `Community 132`, `Community 134`, `Community 135`, `Community 151`, `Community 154`, `Community 169`?**
|
||||
_High betweenness centrality (0.257) - this node is a cross-community bridge._
|
||||
- **Why does `BaseModel` connect `Community 3` to `Community 0`, `Community 1`, `Community 128`, `Community 4`, `Community 6`, `Community 7`, `Community 9`, `Community 10`, `Community 11`, `Community 14`, `Community 15`, `Community 18`, `Community 148`, `Community 149`, `Community 22`, `Community 23`, `Community 24`, `Community 152`, `Community 20`, `Community 156`, `Community 29`, `Community 158`, `Community 30`, `Community 157`, `Community 159`, `Community 160`, `Community 35`, `Community 32`, `Community 38`, `Community 39`, `Community 40`, `Community 42`, `Community 48`, `Community 61`, `Community 65`, `Community 76`, `Community 77`, `Community 104`, `Community 111`, `Community 112`, `Community 114`, `Community 126`, `Community 127`?**
|
||||
_High betweenness centrality (0.058) - this node is a cross-community bridge._
|
||||
- **Why does `int` connect `Community 55` to `Community 0`, `Community 1`, `Community 2`, `Community 4`, `Community 5`, `Community 7`, `Community 8`, `Community 9`, `Community 10`, `Community 11`, `Community 12`, `Community 14`, `Community 15`, `Community 17`, `Community 18`, `Community 21`, `Community 22`, `Community 23`, `Community 24`, `Community 25`, `Community 26`, `Community 29`, `Community 30`, `Community 32`, `Community 33`, `Community 35`, `Community 37`, `Community 38`, `Community 41`, `Community 42`, `Community 43`, `Community 44`, `Community 45`, `Community 46`, `Community 47`, `Community 48`, `Community 50`, `Community 51`, `Community 52`, `Community 53`, `Community 54`, `Community 58`, `Community 59`, `Community 66`, `Community 67`, `Community 68`, `Community 69`, `Community 70`, `Community 71`, `Community 72`, `Community 73`, `Community 74`, `Community 76`, `Community 78`, `Community 79`, `Community 80`, `Community 83`, `Community 86`, `Community 91`, `Community 92`, `Community 93`, `Community 94`, `Community 95`, `Community 96`, `Community 97`, `Community 98`, `Community 99`, `Community 101`, `Community 102`, `Community 104`, `Community 105`, `Community 108`, `Community 111`, `Community 112`, `Community 118`, `Community 121`, `Community 135`, `Community 154`?**
|
||||
_High betweenness centrality (0.040) - this node is a cross-community bridge._
|
||||
- **Are the 337 inferred relationships involving `str` (e.g. with `AdvanceApplyCreate` and `AdvanceApplyOut`) actually correct?**
|
||||
_`str` has 337 INFERRED edges - model-reasoned connections that need verification._
|
||||
- **Are the 333 inferred relationships involving `Session` (e.g. with `AdvanceApplyCreate` and `AdvanceApplyOut`) actually correct?**
|
||||
@@ -756,4 +810,4 @@ _Questions this graph is uniquely positioned to answer:_
|
||||
- **Are the 222 inferred relationships involving `int` (e.g. with `AdvanceApplyCreate` and `AdvanceApplyOut`) actually correct?**
|
||||
_`int` has 222 INFERRED edges - model-reasoned connections that need verification._
|
||||
- **What connects `定期 ping MySQL,避免远程库/NAT 回收空闲 TCP 后首请求长时间卡住。`, `数据库异常:返回 JSON 并保留 CORS 头,避免浏览器只报跨域错误`, `未捕获异常:统一 JSON 500,确保跨域场景下仍有 Access-Control-Allow-Origin` to the rest of the system?**
|
||||
_1446 weakly-connected nodes found - possible documentation gaps or missing edges._
|
||||
_1493 weakly-connected nodes found - possible documentation gaps or missing edges._
|
||||
backend/graphify-out/cache/ast/06c9f3deab3472b20429c12a618ae2d4d6efd3afa2353ac44cd52651a596af4c.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_crm_opportunity_follow_py", "label": "opportunity_follow.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L1"}, {"id": "crm_opportunity_follow_total_pages", "label": "total_pages()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L17"}, {"id": "int", "label": "int", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L17"}, {"id": "crm_opportunity_follow_parse_optional_datetime", "label": "_parse_optional_datetime()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L26"}, {"id": "any", "label": "Any", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L26"}, {"id": "datetime", "label": "datetime", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L26"}, {"id": "crm_opportunity_follow_row_to_dict", "label": "_row_to_dict()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L42"}, {"id": "crmopportunityfollow", "label": "CrmOpportunityFollow", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L42"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L42"}, {"id": "crm_opportunity_follow_recalc_opportunity_follow_stats", "label": "_recalc_opportunity_follow_stats()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L71"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L71"}, {"id": "crm_opportunity_follow_ensure_parent_opportunity", "label": "_ensure_parent_opportunity()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L111"}, {"id": "crmopportunity", "label": "CrmOpportunity", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L111"}, {"id": "crm_opportunity_follow_list_opportunity_follows", "label": "list_opportunity_follows()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L129"}, {"id": "crm_opportunity_follow_list_follows_by_opportunity", "label": "list_follows_by_opportunity()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L159"}, {"id": "crm_opportunity_follow_get_opportunity_follow", "label": "get_opportunity_follow()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L176"}, {"id": "crm_opportunity_follow_create_opportunity_follow", "label": "create_opportunity_follow()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L197"}, {"id": "crm_opportunity_follow_update_opportunity_follow", "label": "update_opportunity_follow()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L243"}, {"id": "crm_opportunity_follow_soft_delete_opportunity_follow", "label": "soft_delete_opportunity_follow()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L287"}, {"id": "bool", "label": "bool", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L287"}], "edges": [{"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_crm_opportunity_follow_py", "target": "math", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py", "source_location": "L4", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_crm_opportunity_follow_py", "target": "datetime", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py",Line truncated
|
||||
backend/graphify-out/cache/ast/0e0620d2878e4b28c560daf8095d30e8e8a281653d40baf01ef94e899ce114e8.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_services_pl_plan_service_py", "label": "pl_plan_service.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L1"}, {"id": "services_pl_plan_service_determine_plan_type", "label": "determine_plan_type()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L42"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L42"}, {"id": "services_pl_plan_service_is_ton_product_unit", "label": "is_ton_product_unit()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L50"}, {"id": "bool", "label": "bool", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L50"}, {"id": "services_pl_plan_service_product_quantity", "label": "_product_quantity()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L57"}, {"id": "planproductcreate", "label": "PlanProductCreate", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L57"}, {"id": "decimal", "label": "Decimal", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L57"}, {"id": "services_pl_plan_service_product_create_actual_qty", "label": "_product_create_actual_qty()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L67"}, {"id": "services_pl_plan_service_plan_product_actual_qty", "label": "_plan_product_actual_qty()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L73"}, {"id": "plplanproduct", "label": "PlPlanProduct", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L73"}, {"id": "services_pl_plan_service_sum_product_actual_quantities", "label": "sum_product_actual_quantities()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L79"}, {"id": "services_pl_plan_service_sum_plan_product_actual_quantities", "label": "sum_plan_product_actual_quantities()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L83"}, {"id": "services_pl_plan_service_estimate_qty_fields", "label": "_estimate_qty_fields()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L87"}, {"id": "services_pl_plan_service_build_plan_product", "label": "build_plan_product()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L98"}, {"id": "int", "label": "int", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L98"}, {"id": "services_pl_plan_service_apply_vehicle_snapshot_to_plan_transport", "label": "_apply_vehicle_snapshot_to_plan_transport()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L145"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L145"}, {"id": "plplan", "label": "PlPlan", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L145"}, {"id": "pltransport", "label": "PlTransport", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L145"}, {"id": "plvehicle", "label": "PlVehicle", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L145"}, {"id": "services_pl_plan_service_resolve_driver", "label": "_resolve_driver()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L163"}, {"id": "services_pl_plan_service_calc_freight_amount", "label": "_calc_freight_amount()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py", "source_location": "L208"}, {"id": "services_pl_plan_service_fetch_hazard_flags_mapLine truncated
|
||||
backend/graphify-out/cache/ast/214a2bf42815653f798194e62db231cbec4d3c6a05efba8cb148a865689bebe2.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_services_web_auth_roles_py", "label": "web_auth_roles.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/web_auth_roles.py", "source_location": "L1"}, {"id": "services_web_auth_roles_map_role_name_to_web", "label": "_map_role_name_to_web()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/web_auth_roles.py", "source_location": "L13"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/web_auth_roles.py", "source_location": "L13"}, {"id": "services_web_auth_roles_role_rows_for_user", "label": "_role_rows_for_user()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/web_auth_roles.py", "source_location": "L25"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/web_auth_roles.py", "source_location": "L25"}, {"id": "services_web_auth_roles_user_has_bound_roles", "label": "user_has_bound_roles()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/web_auth_roles.py", "source_location": "L42"}, {"id": "bool", "label": "bool", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/web_auth_roles.py", "source_location": "L42"}, {"id": "services_web_auth_roles_resolve_web_role_for_user", "label": "resolve_web_role_for_user()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/web_auth_roles.py", "source_location": "L53"}, {"id": "services_web_auth_roles_iter_user_menu_perm_codes", "label": "iter_user_menu_perm_codes()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/web_auth_roles.py", "source_location": "L71"}, {"id": "services_web_auth_roles_rationale_1", "label": "Web \u7ba1\u7406\u7aef\u767b\u5f55\uff1a\u57fa\u4e8e V0.3 sys_user / sys_user_role / sys_role \u7684\u89d2\u8272\u4e0e\u83dc\u5355\u6743\u9650\u89e3\u6790", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/web_auth_roles.py", "source_location": "L1"}, {"id": "services_web_auth_roles_rationale_14", "label": "\u5c06 sys_role.role_name \u6620\u5c04\u4e3a web-admin \u4f7f\u7528\u7684 role \u5b57\u7b26\u4e32\u3002", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/web_auth_roles.py", "source_location": "L14"}, {"id": "services_web_auth_roles_rationale_43", "label": "\u7528\u6237\u662f\u5426\u5728 sys_user_role \u4e2d\u7ed1\u5b9a\u4e86\u81f3\u5c11\u4e00\u4e2a\u6709\u6548\u89d2\u8272\u3002", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/web_auth_roles.py", "source_location": "L43"}, {"id": "services_web_auth_roles_rationale_54", "label": "\u6839\u636e\u7528\u6237\u5173\u8054\u89d2\u8272\u89e3\u6790 Web \u7aef role\uff08admin / super_admin\uff09\u3002", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/web_auth_roles.py", "source_location": "L54"}, {"id": "services_web_auth_roles_rationale_74", "label": "\u7528\u6237\u901a\u8fc7\u89d2\u8272\u5173\u8054\u5f97\u5230\u7684\u83dc\u5355\u6743\u9650\u6807\u8bc6\uff08sys_menu.perms\uff0c\u7a7a\u5219\u9000\u56de\u83dc\u5355 id\uff09\u3002", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/web_auth_roles.py", "source_location": "L74"}], "edges": [{"source": "users_zlwu_work_00code_shanxia2_0_backend_app_services_web_auth_roles_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/web_auth_roles.py", "source_location": "L4", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_services_web_auth_roles_py", "target": "sqlalchemy", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/web_auth_roles.py", "source_location": "L6", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_services_web_auth_roles_py", "target": "sqlalchemy_orm", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/web_auth_roles.py", "source_location": "L7", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_services_web_auth_roles_py", "target": "app_core_config", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/web_auth_roles.py", "source_location": "L9", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_appLine truncated
|
||||
backend/graphify-out/cache/ast/2aeb56dda907dad40edb4072170f47422c642ce2547bf0d02e4193fb836b869b.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_contract_routes_py", "label": "crm_contract_routes.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_contract_routes.py", "source_location": "L1"}, {"id": "api_crm_contract_routes_register_contract_routes", "label": "register_contract_routes()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_contract_routes.py", "source_location": "L75"}, {"id": "api_crm_contract_routes_rationale_1", "label": "CRM \u5408\u540c API \u8def\u7531 \u2014 GET/PUT/DELETE/POST \u5bf9\u9f50\u539f Java CRM", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_contract_routes.py", "source_location": "L1"}], "edges": [{"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_contract_routes_py", "target": "datetime", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_contract_routes.py", "source_location": "L4", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_contract_routes_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_contract_routes.py", "source_location": "L5", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_contract_routes_py", "target": "fastapi", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_contract_routes.py", "source_location": "L7", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_contract_routes_py", "target": "sqlalchemy_orm", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_contract_routes.py", "source_location": "L8", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_contract_routes_py", "target": "app_core_database", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_contract_routes.py", "source_location": "L10", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_contract_routes_py", "target": "app_core_menu_guard", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_contract_routes.py", "source_location": "L11", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_contract_routes_py", "target": "app_core_oper_log_decorator", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_contract_routes.py", "source_location": "L12", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_contract_routes_py", "target": "app_core_response", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_contract_routes.py", "source_location": "L13", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_contract_routes_py", "target": "app_core_tenant_context", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_contract_routes.py", "source_location": "L14", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_contract_routes_py", "target": "app_crud_crm_data_scope_helpers", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_contract_routes.py", "source_location": "L15", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_contract_routes_py", "target": "app_crud_crm_contract", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_contract_routes.py", "source_location": "L16", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_contract_routes_py", "target": "app_crud_crm_contract_approval", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_contract_routes.py", "source_location": "L47", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_contract_routes_py", "target": "app_schemas_crm_contract", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shLine truncated
|
||||
backend/graphify-out/cache/ast/6af8eb3c1f6c7ad34cd62f1e82e9c6b96737ffd48ba74dda7c5f888c55dd11cd.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_tests_test_pl_dibang_plan_quantity_py", "label": "test_pl_dibang_plan_quantity.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/tests/test_pl_dibang_plan_quantity.py", "source_location": "L1"}, {"id": "tests_test_pl_dibang_plan_quantity_test_apply_plan_quantity_delta_accumulates", "label": "test_apply_plan_quantity_delta_accumulates()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/tests/test_pl_dibang_plan_quantity.py", "source_location": "L16"}, {"id": "tests_test_pl_dibang_plan_quantity_test_apply_plan_quantity_delta_never_below_zero", "label": "test_apply_plan_quantity_delta_never_below_zero()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/tests/test_pl_dibang_plan_quantity.py", "source_location": "L23"}, {"id": "tests_test_pl_dibang_plan_quantity_test_resolve_link_contributed_qty_prefers_link_field", "label": "test_resolve_link_contributed_qty_prefers_link_field()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/tests/test_pl_dibang_plan_quantity.py", "source_location": "L29"}, {"id": "tests_test_pl_dibang_plan_quantity_test_resolve_link_contributed_qty_falls_back_to_inventory", "label": "test_resolve_link_contributed_qty_falls_back_to_inventory()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/tests/test_pl_dibang_plan_quantity.py", "source_location": "L35"}, {"id": "tests_test_pl_dibang_plan_quantity_test_resolve_link_contributed_qty_zero_when_missing", "label": "test_resolve_link_contributed_qty_zero_when_missing()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/tests/test_pl_dibang_plan_quantity.py", "source_location": "L41"}, {"id": "tests_test_pl_dibang_plan_quantity_test_revoke_subtracts_plan_quantity_on_cancel", "label": "test_revoke_subtracts_plan_quantity_on_cancel()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/tests/test_pl_dibang_plan_quantity.py", "source_location": "L48"}, {"id": "tests_test_pl_dibang_plan_quantity_test_revoke_blocks_when_weighing_exists", "label": "test_revoke_blocks_when_weighing_exists()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/tests/test_pl_dibang_plan_quantity.py", "source_location": "L102"}], "edges": [{"source": "users_zlwu_work_00code_shanxia2_0_backend_tests_test_pl_dibang_plan_quantity_py", "target": "decimal", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/tests/test_pl_dibang_plan_quantity.py", "source_location": "L2", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_tests_test_pl_dibang_plan_quantity_py", "target": "unittest_mock", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/tests/test_pl_dibang_plan_quantity.py", "source_location": "L3", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_tests_test_pl_dibang_plan_quantity_py", "target": "pytest", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/tests/test_pl_dibang_plan_quantity.py", "source_location": "L5", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_tests_test_pl_dibang_plan_quantity_py", "target": "app_models_db_models", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/tests/test_pl_dibang_plan_quantity.py", "source_location": "L7", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_tests_test_pl_dibang_plan_quantity_py", "target": "app_models_pl_models", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/tests/test_pl_dibang_plan_quantity.py", "source_location": "L8", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_tests_test_pl_dibang_plan_quantity_py", "target": "app_services_pl_dibang_bridge_service", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/tests/test_pl_dibang_plan_quantity.py", "source_location": "L9", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_tests_test_pl_dibang_plan_quantity_py", "target": "tests_test_pl_dibang_plan_quantity_test_apply_plan_quantity_delta_accumulates", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/tests/test_pl_dibang_plan_quantity.py", "source_location": "L16", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_tests_test_pl_dibang_plan_quantity_py", "target": "tests_test_pl_dibang_plan_quantity_test_apply_planLine truncated
|
||||
backend/graphify-out/cache/ast/6d1fb7bc06a4f16c729b1b226377ffa38b9b47bf1cc75310dcefbb8f6e480574.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_crm_product_py", "label": "product.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L1"}, {"id": "crm_product_total_pages", "label": "total_pages()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L24"}, {"id": "int", "label": "int", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L24"}, {"id": "crm_product_category_row_to_dict", "label": "_category_row_to_dict()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L34"}, {"id": "crmproductcategory", "label": "CrmProductCategory", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L34"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L34"}, {"id": "any", "label": "Any", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L34"}, {"id": "crm_product_list_categories", "label": "list_categories()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L50"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L50"}, {"id": "crm_product_build_category_tree", "label": "_build_category_tree()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L77"}, {"id": "crm_product_collect_category_with_ancestors", "label": "_collect_category_with_ancestors()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L88"}, {"id": "crm_product_list_category_tree", "label": "list_category_tree()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L104"}, {"id": "crm_product_get_category", "label": "get_category()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L137"}, {"id": "crm_product_create_category", "label": "create_category()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L149"}, {"id": "crm_product_update_category", "label": "update_category()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L186"}, {"id": "crm_product_soft_delete_category", "label": "soft_delete_category()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L225"}, {"id": "crm_product_category_code_exists", "label": "category_code_exists()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L265"}, {"id": "bool", "label": "bool", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L265"}, {"id": "crm_product_product_row_to_dict", "label": "_product_row_to_dict()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L285"}, {"id": "crmproduct", "label": "CrmProduct", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L285"}, {"id": "crm_product_list_products", "label": "list_products()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L316"}, {"id": "crm_product_get_product", "label": "get_product()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L362"}, {"id": "crm_product_get_product_by_no", "label": "get_product_by_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L374"}, {"id": "crm_product_max_sxwl_product_seq", "label": "_max_sxwl_product_seq()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L393"}, {"id": "crm_product_generate_product_no", "label": "generate_product_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py", "source_location": "L414"}, {"id": "crm_product_generate_barcode", "label"Line truncated
|
||||
backend/graphify-out/cache/ast/94ec0dfb5ab226b57484e15b6555f58df40e7943dd1149ac0eb3f8f9b03c104e.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "label": "crm_quote_routes.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L1"}, {"id": "api_crm_quote_routes_register_quote_routes", "label": "register_quote_routes()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L83"}, {"id": "api_crm_quote_routes_rationale_1", "label": "CRM \u62a5\u4ef7\u5355 API \u8def\u7531 \u2014 \u6302\u63a5\u5230 crm.router", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L1"}], "edges": [{"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "datetime", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L4", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "fastapi", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L6", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "sqlalchemy_orm", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L7", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_core_database", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L9", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_core_menu_guard", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L10", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_core_response", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L11", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_core_tenant_context", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L12", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_crud_crm_data_scope_helpers", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L13", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_crud_crm_quote", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L14", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_crud_crm_quote_approval", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L36", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_crud_crm_quote_product", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L42", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_crud_crm_quote_share_log", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L48", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_schemas_crm_quote", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L49", "weight": 1.0}, {"source": "uLine truncated
|
||||
backend/graphify-out/cache/ast/b2362db2f2df750afc1c2263f5e6ac1ccd5daa8b93ad5650d56028f452597837.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_crud_crm_contract_py", "label": "contract.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L1"}, {"id": "crm_contract_ensure_contract_in_scope", "label": "_ensure_contract_in_scope()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L48"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L48"}, {"id": "crmcontract", "label": "CrmContract", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L48"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L48"}, {"id": "crm_contract_get_contract_row_in_scope", "label": "_get_contract_row_in_scope()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L52"}, {"id": "crm_contract_total_pages", "label": "total_pages()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L69"}, {"id": "int", "label": "int", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L69"}, {"id": "crm_contract_decimal_value", "label": "_decimal_value()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L75"}, {"id": "any", "label": "Any", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L75"}, {"id": "decimal", "label": "Decimal", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L75"}, {"id": "crm_contract_parse_optional_date", "label": "_parse_optional_date()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L83"}, {"id": "date", "label": "date", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L83"}, {"id": "crm_contract_parse_optional_datetime", "label": "_parse_optional_datetime()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L101"}, {"id": "datetime", "label": "datetime", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L101"}, {"id": "crm_contract_get_field", "label": "_get_field()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L117"}, {"id": "crm_contract_resolve_user_display_name", "label": "_resolve_user_display_name()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L124"}, {"id": "crm_contract_is_blank", "label": "_is_blank()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L134"}, {"id": "bool", "label": "bool", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L134"}, {"id": "crm_contract_first_non_blank", "label": "_first_non_blank()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L138"}, {"id": "crm_contract_get_default_invoice_info", "label": "_get_default_invoice_info()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L146"}, {"id": "crm_contract_build_customer_address", "label": "_build_customer_address()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L156"}, {"id": "crm_contract_row_to_dict", "label": "_row_to_dict()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L168"}, {"id": "crm_contract_contract_row_to_dict", "label": "contract_row_to_dict()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L245"}, {"id": "crm_contract_build_contract_filters", "label": "_build_contract_filters()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py", "source_location": "L249"}, {"id": "query", "label": "Query", "file_type": "code", "source_file": Line truncated
|
||||
backend/graphify-out/cache/ast/b8d626eca1b0fc6b0216a66afb1cf234155310d3077d940d5158111f873a414b.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_crm_product_py", "label": "product.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py", "source_location": "L1"}, {"id": "crm_product_productcategorycreatebody", "label": "ProductCategoryCreateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py", "source_location": "L12"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "crm_product_productcategoryupdatebody", "label": "ProductCategoryUpdateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py", "source_location": "L26"}, {"id": "crm_product_categorydeletebody", "label": "CategoryDeleteBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py", "source_location": "L41"}, {"id": "crm_product_productupdatestatusbody", "label": "ProductUpdateStatusBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py", "source_location": "L47"}, {"id": "crm_product_productlistbody", "label": "ProductListBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py", "source_location": "L57"}, {"id": "crm_product_productcreatebody", "label": "ProductCreateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py", "source_location": "L69"}, {"id": "crm_product_productupdatebody", "label": "ProductUpdateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py", "source_location": "L101"}, {"id": "crm_product_productdetailbody", "label": "ProductDetailBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py", "source_location": "L134"}, {"id": "crm_product_productdeletebody", "label": "ProductDeleteBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py", "source_location": "L140"}, {"id": "crm_product_productoptionsbody", "label": "ProductOptionsBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py", "source_location": "L147"}, {"id": "crm_product_rationale_1", "label": "CRM \u4ea7\u54c1\u6a21\u5757 Pydantic Schemas", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py", "source_location": "L1"}], "edges": [{"source": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_crm_product_py", "target": "decimal", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py", "source_location": "L3", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_crm_product_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py", "source_location": "L4", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_crm_product_py", "target": "pydantic", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py", "source_location": "L6", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_crm_product_py", "target": "crm_product_productcategorycreatebody", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py", "source_location": "L12", "weight": 1.0}, {"source": "crm_product_productcategorycreatebody", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py", "source_location": "L12", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_crm_product_py", "target": "crm_product_productcategoryupdatebody", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py", "source_location": "L26", "weight": 1.0}, {"source": "crm_product_productcategoryupdatebody", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py", "source_location": "L26", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_crm_product_py", "target": "crm_product_categorydeletebody", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxiaLine truncated
|
||||
backend/graphify-out/cache/ast/b91167282158aeb11046dccc4bf2f29a13307a0413b3d7b3c860b17d37f8ad4a.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_crm_customer_py", "label": "customer.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L1"}, {"id": "crm_customer_customerlistbody", "label": "CustomerListBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L12"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "crm_customer_customerlistbody_to_list_kwargs", "label": ".to_list_kwargs()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L39"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L39"}, {"id": "crm_customer_customercreatebody", "label": "CustomerCreateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L72"}, {"id": "crm_customer_customercreatebody_forbid_legacy_invoice_fields", "label": "._forbid_legacy_invoice_fields()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L110"}, {"id": "crm_customer_customerupdatebody", "label": "CustomerUpdateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L135"}, {"id": "crm_customer_customerupdatebody_forbid_legacy_invoice_fields", "label": "._forbid_legacy_invoice_fields()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L174"}, {"id": "crm_customer_customerdeletebody", "label": "CustomerDeleteBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L199"}, {"id": "crm_customer_customerdetailbody", "label": "CustomerDetailBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L207"}, {"id": "crm_customer_customertransferbody", "label": "CustomerTransferBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L214"}, {"id": "crm_customer_customerintopoolbody", "label": "CustomerIntoPoolBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L226"}, {"id": "crm_customer_customerdealbody", "label": "CustomerDealBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L236"}, {"id": "crm_customer_customerclaimbody", "label": "CustomerClaimBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L248"}, {"id": "crm_customer_customerassignpoolbody", "label": "CustomerAssignPoolBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L257"}, {"id": "crm_customer_customercheckduplicatebody", "label": "CustomerCheckDuplicateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L268"}, {"id": "crm_customer_rationale_1", "label": "CRM \u5ba2\u6237\u7ba1\u7406 Pydantic Schemas", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L1"}, {"id": "crm_customer_rationale_40", "label": "\u89e3\u6790\u5217\u8868\u67e5\u8be2\u53c2\u6570\uff0c\u4f9b API \u4e0e CRUD \u5171\u7528\u3002", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L40"}], "edges": [{"source": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_crm_customer_py", "target": "decimal", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L3", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_crm_customer_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py", "source_location": "L4", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_schemas_crm_customer_py", "target": "pydantic", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/schemasLine truncated
|
||||
backend/graphify-out/cache/ast/bbe64081c9790941eb2fa8f9c18ee39daf151068b3503f1fdb74c2cb9f7b5b45.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_core_config_py", "label": "config.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/config.py", "source_location": "L1"}, {"id": "core_config_settings", "label": "Settings", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/config.py", "source_location": "L7"}, {"id": "basesettings", "label": "BaseSettings", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "core_config_config", "label": "Config", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/config.py", "source_location": "L173"}], "edges": [{"source": "users_zlwu_work_00code_shanxia2_0_backend_app_core_config_py", "target": "pydantic_settings", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/config.py", "source_location": "L1", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_core_config_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/config.py", "source_location": "L2", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_core_config_py", "target": "pathlib", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/config.py", "source_location": "L3", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_core_config_py", "target": "os", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/config.py", "source_location": "L4", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_core_config_py", "target": "core_config_settings", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/config.py", "source_location": "L7", "weight": 1.0}, {"source": "core_config_settings", "target": "basesettings", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/config.py", "source_location": "L7", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_core_config_py", "target": "core_config_config", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/core/config.py", "source_location": "L173", "weight": 1.0}], "raw_calls": []}
|
||||
backend/graphify-out/cache/ast/bfc69a8677607af514a8b224ca67360a01cf2945b11294b5041e018b63a32d36.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_api_public_py", "label": "public.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L1"}, {"id": "api_public_avatar_url_field", "label": "_avatar_url_field()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L39"}, {"id": "sysuser", "label": "SysUser", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L39"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L39"}, {"id": "api_public_build_user_info_payload", "label": "_build_user_info_payload()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L47"}, {"id": "api_public_jwt_user_payload", "label": "_jwt_user_payload()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L63"}, {"id": "api_public_web_login_rsa_public_key", "label": "web_login_rsa_public_key()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L76"}, {"id": "api_public_web_login", "label": "web_login()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L83"}, {"id": "request", "label": "Request", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L83"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L83"}, {"id": "api_public_refresh_token", "label": "refresh_token()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L141"}, {"id": "api_public_logout", "label": "logout()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L203"}, {"id": "api_public_get_current_user_profile", "label": "get_current_user_profile()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L222"}, {"id": "api_public_get_web_auth_menu_codes", "label": "get_web_auth_menu_codes()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L255"}, {"id": "api_public_public_quote_share", "label": "public_quote_share()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L304"}, {"id": "api_public_rationale_1", "label": "\u516c\u5171\u63a5\u53e3\uff08Web \u767b\u5f55\u3001\u5237\u65b0 Token\u3001\u767b\u51fa\uff1b\u7528\u6237\u6a21\u578b\u5bf9\u9f50 docs/sql/shanxia2.0.sql \u57fa\u7ebf\uff09", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L1"}, {"id": "api_public_rationale_48", "label": "\u4e0e web-admin `userInfo` \u5b57\u6bb5\u7ea6\u5b9a\u4e00\u81f4\u3002", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L48"}, {"id": "api_public_rationale_77", "label": "Web \u7aef\u767b\u5f55\u7528 RSA \u516c\u94a5\uff08PKCS#1 v1.5\uff0c\u4e0e JSEncrypt \u4e00\u81f4\uff09", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L77"}, {"id": "api_public_rationale_88", "label": "Web \u7aef\u767b\u5f55\uff1a\u8d26\u53f7\u4e3a sys_user.user_no\uff0c\u5bc6\u7801\u4e3a sys_user.password\uff08bcrypt\uff09\u3002", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L88"}, {"id": "api_public_rationale_204", "label": "\u767b\u51fa\uff1a\u540a\u9500 Redis \u4f1a\u8bdd\uff08\u82e5\u5df2\u914d\u7f6e REDIS_HOST\uff09\u3002", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L204"}, {"id": "api_public_rationale_223", "label": "\u83b7\u53d6\u5f53\u524d\u7528\u6237\u4fe1\u606f\uff08\u4e0e sys_user \u5b57\u6bb5\u5bf9\u5e94\uff09\u3002", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L223"}, {"id": "api_public_rationale_256", "label": "\u83b7\u53d6\u5f53\u524d\u7528\u6237\u83dc\u5355\u6743\u9650\u6807\u8bc6\uff08sys_menu.perms\uff0c\u7ecf\u89d2\u8272\u7ee7\u627f\uff09\u3002", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py", "source_location": "L256"}, {"id": "api_public_rationale_310", "label": "\Line truncated
|
||||
backend/graphify-out/cache/ast/c00844927ec2083a9df9c86cbb58013a7645f81d2ffbb751d2b782ad1612a59f.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_models_pl_models_py", "label": "pl_models.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L1"}, {"id": "models_pl_models_plcarrier", "label": "PlCarrier", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L28"}, {"id": "base", "label": "Base", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "models_pl_models_plvehicle", "label": "PlVehicle", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L57"}, {"id": "models_pl_models_plpersonnel", "label": "PlPersonnel", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L83"}, {"id": "models_pl_models_pltransportaddress", "label": "PlTransportAddress", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L105"}, {"id": "models_pl_models_plcert", "label": "PlCert", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L129"}, {"id": "models_pl_models_pldriver", "label": "PlDriver", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L155"}, {"id": "models_pl_models_plvehiclepersonnel", "label": "PlVehiclePersonnel", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L181"}, {"id": "models_pl_models_plvehicledriver", "label": "PlVehicleDriver", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L202"}, {"id": "models_pl_models_plplan", "label": "PlPlan", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L227"}, {"id": "models_pl_models_plplanproduct", "label": "PlPlanProduct", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L326"}, {"id": "models_pl_models_plplanchange", "label": "PlPlanChange", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L382"}, {"id": "models_pl_models_pltransport", "label": "PlTransport", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L409"}, {"id": "models_pl_models_pltransportdblink", "label": "PlTransportDbLink", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L471"}, {"id": "models_pl_models_plplantransport", "label": "PlPlanTransport", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L494"}, {"id": "models_pl_models_pltransportsitelink", "label": "PlTransportSiteLink", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L513"}, {"id": "models_pl_models_pltransportapproval", "label": "PlTransportApproval", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L546"}, {"id": "models_pl_models_plfreightsettlement", "label": "PlFreightSettlement", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L575"}, {"id": "models_pl_models_plfreightsettlementapproval", "label": "PlFreightSettlementApproval", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L607"}, {"id": "models_pl_models_plfreightsettlementtransport", "label": "PlFreightSettlementTransport", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L634"}, {"id": "models_pl_models_plremittancevoucher", "label": "PlRemittanceVoucher", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L654"}, {"id": "models_pl_models_plinventory", "label": "PlInventory", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L681"}, {"id": "models_pl_models_plinventorytransaction", "label": "PlInventoryTransaction", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py", "source_location": "L713"}, {"id": "models_pl_models_plinventorywarning", "label": "PlInventoryWarning", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0Line truncated
|
||||
backend/graphify-out/cache/ast/c4faf32ba7b1bd6adc20fa11ae70e3d9cb1fda34d544a6062a14fb562f2cf85a.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_py", "label": "crm.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L1"}, {"id": "api_crm_category_tree", "label": "category_tree()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L327"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L327"}, {"id": "api_crm_customer_list", "label": "customer_list()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L358"}, {"id": "customerlistbody", "label": "CustomerListBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L358"}, {"id": "api_crm_customer_detail", "label": "customer_detail()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L392"}, {"id": "api_crm_customer_create", "label": "customer_create()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L419"}, {"id": "customercreatebody", "label": "CustomerCreateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L419"}, {"id": "api_crm_customer_update", "label": "customer_update()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L449"}, {"id": "customerupdatebody", "label": "CustomerUpdateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L449"}, {"id": "api_crm_customer_delete", "label": "customer_delete()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L476"}, {"id": "customerdeletebody", "label": "CustomerDeleteBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L476"}, {"id": "api_crm_customer_generate_no", "label": "customer_generate_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L504"}, {"id": "api_crm_category_add", "label": "category_add()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L520"}, {"id": "productcategorycreatebody", "label": "ProductCategoryCreateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L520"}, {"id": "api_crm_category_update", "label": "category_update()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L539"}, {"id": "productcategoryupdatebody", "label": "ProductCategoryUpdateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L539"}, {"id": "api_crm_category_delete", "label": "category_delete()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L559"}, {"id": "categorydeletebody", "label": "CategoryDeleteBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L559"}, {"id": "api_crm_product_list", "label": "product_list()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L579"}, {"id": "productlistbody", "label": "ProductListBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L579"}, {"id": "api_crm_product_detail", "label": "product_detail()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L615"}, {"id": "productdetailbody", "label": "ProductDetailBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L615"}, {"id": "api_crm_product_add", "label": "product_add()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L636"}, {"id": "productcreatebody", "label": "ProductCreateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L636"}, {"id": "api_crm_product_update", "label": "product_update()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L660"}, {"id": "productupdatebody", "label": "ProductUpdateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.Line truncated
|
||||
backend/graphify-out/cache/ast/c506eb84c104ce63b98cde5b16644f16ddcf4e3aa335089bedcb4c267d21fc0d.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_services_pl_dibang_bridge_service_py", "label": "pl_dibang_bridge_service.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L1"}, {"id": "services_pl_dibang_bridge_service_dibanginventorylinkresult", "label": "DibangInventoryLinkResult", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L27"}, {"id": "services_pl_dibang_bridge_service_resolve_partner_company", "label": "resolve_partner_company()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L36"}, {"id": "plplan", "label": "PlPlan", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L36"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L36"}, {"id": "services_pl_dibang_bridge_service_normalize_to_date", "label": "_normalize_to_date()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L49"}, {"id": "date", "label": "date", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L49"}, {"id": "datetime", "label": "datetime", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L49"}, {"id": "services_pl_dibang_bridge_service_normalize_plan_date", "label": "_normalize_plan_date()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L61"}, {"id": "services_pl_dibang_bridge_service_resolve_dibang_date", "label": "_resolve_dibang_date()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L73"}, {"id": "services_pl_dibang_bridge_service_month_range", "label": "_month_range()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L84"}, {"id": "services_pl_dibang_bridge_service_plan_type_to_business_type", "label": "_plan_type_to_business_type()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L91"}, {"id": "services_pl_dibang_bridge_service_product_quantity", "label": "_product_quantity()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L99"}, {"id": "plplanproduct", "label": "PlPlanProduct", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L99"}, {"id": "float", "label": "float", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L99"}, {"id": "services_pl_dibang_bridge_service_apply_plan_quantity_delta", "label": "_apply_plan_quantity_delta()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L106"}, {"id": "dbpurchaseplan", "label": "DbPurchasePlan", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L106"}, {"id": "services_pl_dibang_bridge_service_resolve_link_contributed_qty", "label": "_resolve_link_contributed_qty()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L117"}, {"id": "pltransportdblink", "label": "PlTransportDbLink", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L117"}, {"id": "dbinboundoutbound", "label": "DbInboundOutbound", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L117"}, {"id": "services_pl_dibang_bridge_service_subtract_plan_contribution", "label": "_subtract_plan_contribution()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L128"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_locaLine truncated
|
||||
backend/graphify-out/cache/ast/c51b70766aae85dbc537bf5b48a2c350c73ea8e93bcad2a9660d8d45c6d0979d.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "label": "crm_quote_routes.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L1"}, {"id": "api_crm_quote_routes_register_quote_routes", "label": "register_quote_routes()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L83"}, {"id": "api_crm_quote_routes_rationale_1", "label": "CRM \u62a5\u4ef7\u5355 API \u8def\u7531 \u2014 \u6302\u63a5\u5230 crm.router", "file_type": "rationale", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L1"}], "edges": [{"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "datetime", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L4", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "fastapi", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L6", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "sqlalchemy_orm", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L7", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_core_database", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L9", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_core_menu_guard", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L10", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_core_response", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L11", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_core_tenant_context", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L12", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_crud_crm_data_scope_helpers", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L13", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_crud_crm_quote", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L14", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_crud_crm_quote_approval", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L36", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_crud_crm_quote_product", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L42", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_crud_crm_quote_share_log", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L48", "weight": 1.0}, {"source": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_quote_routes_py", "target": "app_schemas_crm_quote", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py", "source_location": "L49", "weight": 1.0}, {"source": "uLine truncated
|
||||
backend/graphify-out/cache/ast/dd3e913b1f791dce0fdc1aeb0c325231c12ca9611bead88b23cbdaa71707f8d4.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_services_pl_dibang_bridge_service_py", "label": "pl_dibang_bridge_service.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L1"}, {"id": "services_pl_dibang_bridge_service_dibanginventorylinkresult", "label": "DibangInventoryLinkResult", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L27"}, {"id": "services_pl_dibang_bridge_service_resolve_partner_company", "label": "resolve_partner_company()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L36"}, {"id": "plplan", "label": "PlPlan", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L36"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L36"}, {"id": "services_pl_dibang_bridge_service_normalize_to_date", "label": "_normalize_to_date()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L49"}, {"id": "date", "label": "date", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L49"}, {"id": "datetime", "label": "datetime", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L49"}, {"id": "services_pl_dibang_bridge_service_normalize_plan_date", "label": "_normalize_plan_date()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L61"}, {"id": "services_pl_dibang_bridge_service_resolve_dibang_date", "label": "_resolve_dibang_date()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L73"}, {"id": "services_pl_dibang_bridge_service_month_range", "label": "_month_range()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L84"}, {"id": "services_pl_dibang_bridge_service_plan_type_to_business_type", "label": "_plan_type_to_business_type()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L91"}, {"id": "services_pl_dibang_bridge_service_product_quantity", "label": "_product_quantity()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L99"}, {"id": "plplanproduct", "label": "PlPlanProduct", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L99"}, {"id": "float", "label": "float", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L99"}, {"id": "services_pl_dibang_bridge_service_generate_batch_no", "label": "_generate_batch_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L106"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L106"}, {"id": "services_pl_dibang_bridge_service_find_monthly_db_plan", "label": "find_monthly_db_plan()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L126"}, {"id": "dbpurchaseplan", "label": "DbPurchasePlan", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L126"}, {"id": "services_pl_dibang_bridge_service_create_db_plan", "label": "_create_db_plan()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L155"}, {"id": "services_pl_dibang_bridge_service_create_db_inventory", "label": "_create_db_inventory()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L197"}, {"id": "dbinboundoutbound", "label": "DbInboundOutbound", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py", "source_location": "L197"}, Line truncated
|
||||
backend/graphify-out/cache/ast/ea9968ef3f7c4eed6baf5f5c9e17b0129b750cfebfceafb411b1368df340404e.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_zlwu_work_00code_shanxia2_0_backend_app_api_crm_py", "label": "crm.py", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L1"}, {"id": "api_crm_category_tree", "label": "category_tree()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L327"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L327"}, {"id": "api_crm_customer_list", "label": "customer_list()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L358"}, {"id": "customerlistbody", "label": "CustomerListBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L358"}, {"id": "api_crm_customer_detail", "label": "customer_detail()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L392"}, {"id": "api_crm_customer_create", "label": "customer_create()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L419"}, {"id": "customercreatebody", "label": "CustomerCreateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L419"}, {"id": "api_crm_customer_update", "label": "customer_update()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L449"}, {"id": "customerupdatebody", "label": "CustomerUpdateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L449"}, {"id": "api_crm_customer_delete", "label": "customer_delete()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L476"}, {"id": "customerdeletebody", "label": "CustomerDeleteBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L476"}, {"id": "api_crm_customer_generate_no", "label": "customer_generate_no()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L504"}, {"id": "api_crm_category_add", "label": "category_add()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L520"}, {"id": "productcategorycreatebody", "label": "ProductCategoryCreateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L520"}, {"id": "api_crm_category_update", "label": "category_update()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L540"}, {"id": "productcategoryupdatebody", "label": "ProductCategoryUpdateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L540"}, {"id": "api_crm_category_delete", "label": "category_delete()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L560"}, {"id": "categorydeletebody", "label": "CategoryDeleteBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L560"}, {"id": "api_crm_product_list", "label": "product_list()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L580"}, {"id": "productlistbody", "label": "ProductListBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L580"}, {"id": "api_crm_product_detail", "label": "product_detail()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L616"}, {"id": "productdetailbody", "label": "ProductDetailBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L616"}, {"id": "api_crm_product_add", "label": "product_add()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L637"}, {"id": "productcreatebody", "label": "ProductCreateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L637"}, {"id": "api_crm_product_update", "label": "product_update()", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py", "source_location": "L661"}, {"id": "productupdatebody", "label": "ProductUpdateBody", "file_type": "code", "source_file": "/Users/zlwu/work/00code/shanxia2.Line truncated
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"/Users/zlwu/work/00code/shanxia2.0/backend/start-prod.sh":{"size":7357,"mtime_ns":1782639355476060453,"hash":"ca89869446be74a2bf2367b3b28f448580220859a39a84ad9613ac5fe35a8433"},"/Users/zlwu/work/00code/shanxia2.0/backend/start.sh":{"size":9548,"mtime_ns":1780114661446983244,"hash":"4d632863e8a0cd81194b491d83a0218a4157435ae812dec76a6f6197b4d48e57"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/__init__.py":{"size":21,"mtime_ns":1780113982281973984,"hash":"60a04ff08fd97084e83ea809fd13feda6d2848be20ebf309e31fc1dad7523230"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/main.py":{"size":6280,"mtime_ns":1782790852770448643,"hash":"8f9da799977bee7134d87a26ff8434ac5ef8eabb0b7368dd537eee49ad7fd00b"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/tasks/__init__.py":{"size":2207,"mtime_ns":1783208067881286091,"hash":"313e6da0e91d743ce891dd283458c79b50b24c4d909dc96ca7b3fd9d2bfa5013"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/auth.py":{"size":7457,"mtime_ns":1782790841500516545,"hash":"34bac56e1c2cb6215f4b5fa2dc32d77d4b4abca51428819eebf4afd269cc51cf"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_crypto.py":{"size":1870,"mtime_ns":1780108930724444498,"hash":"c04b748aea86890a5971f749523c7b17dbad1ddd6f9f7f07a5679d159c984e33"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/login_crypto.py":{"size":3858,"mtime_ns":1780108930725155710,"hash":"b82d51afabf2293a9cb1fb2e5f994c383948e7f47ea95fe674686b4f3f3342b1"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/config.py":{"size":6698,"mtime_ns":1782790876685112837,"hash":"9d955a6baa70f78ddfcf29407d9711915a04e59a7f616254d06d25d25e8d0327"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/wechat_pay.py":{"size":23585,"mtime_ns":1780108930725768130,"hash":"3d52265ce65d21e7103f94afa1e8547aee0823b5ea6d65c50317436ac81fb4fc"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py":{"size":11971,"mtime_ns":1782701669198208175,"hash":"0b84c32828939e6102bb3647691fa8f04b1f68e0e0e9f28127374cc44e19a11c"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/database.py":{"size":1510,"mtime_ns":1782790847265601586,"hash":"8bc9c8ae62e2be2c6920036582ba448dfcd1711c41cebb62274ff6bc6ee14b0b"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/security.py":{"size":728,"mtime_ns":1780108930725470004,"hash":"df0f01115064f7b8de1c81dbe9a6db39360c236610b68c33b3234a51d7f0025b"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/request_payload.py":{"size":2842,"mtime_ns":1780108930725350836,"hash":"e9937151530ab1417ef297aef59111977725cf90be2a8fa76c00a0618789aba0"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/__init__.py":{"size":61,"mtime_ns":1780108930724312414,"hash":"3dd1842ee59b0c804176b03b4b8a323bd4a252b7feb1bbc32db64f3a8928570e"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/response.py":{"size":1824,"mtime_ns":1780108930725412462,"hash":"f1739352d65ea8287d76e282a59553aa8d63c97e35764c64ac378aa5bd9c347e"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/menu_guard.py":{"size":1611,"mtime_ns":1780108930725223002,"hash":"3e26077288c3620a4780038b891a326206b45bffe00aca545bdae30172487235"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/db_collation.py":{"size":548,"mtime_ns":1780108930725025043,"hash":"0c680e25a18244ca839a7de6ed1f4313b33bc8da118d89c463aee1b6af00c6f9"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/context.py":{"size":421,"mtime_ns":1780108930724824458,"hash":"83a10aee67162374993c10a30bf8bb8bd9c4b35cf48f42c832415ccebd91660f"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/utils.py":{"size":1498,"mtime_ns":1780108930725607296,"hash":"09323693661c1d4560458b86a954128ccff82bbaf6522da0b44f24da99ddf9ea"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/storage.py":{"size":14141,"mtime_ns":1782855136810943216,"hash":"3ae2c413c10898a8adb3d6773870bafbfbc18c739c10e0e8b4f765b4b5ca6c08"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/order_status_compat.py":{"size":2828,"mtime_ns":1780108930725284669,"hash":"5349e5a0745ee6734c4a995337779307adefc46f453906171988a77de2081540"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/exceptions.py":{"size":4946,"mtime_ns":1780108930725084335,"hash":"79c997d964bca1a4f12f2b92fb1f181fdb9e2b3cf72eae39f4cc164d23794fd7"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/data_scope.py":{"size":8043,"mtime_ns":1782648925694296304,"hash":"626dff86937e5cacec634ecbc61cd53d3763d760223a1d4ec7cd11f64e59a220"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/admin_request_json.py":{"size":2669,"mtime_ns":1780108930724378914,"hash":"4f68b3cf84291e7bddad6ecf4bb4d29ca5e1ec246a9f7f9c4cf5909d0ec1174a"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_crypto_middleware.py":{"size":10876,"mtime_ns":1782790001926627098,"hash":"b39f128b6612589ea6ac5babd30752e9585b3af07b82bb1ad53c9e3c0d90816f"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/wechat_message.py":{"size":11194,"mtime_ns":1780108930725684255,"hash":"5086070deb14d99d1a69e419b2299ec4Line truncated
|
||||
{"/Users/zlwu/work/00code/shanxia2.0/backend/start-prod.sh":{"size":7357,"mtime_ns":1782639355476060453,"hash":"ca89869446be74a2bf2367b3b28f448580220859a39a84ad9613ac5fe35a8433"},"/Users/zlwu/work/00code/shanxia2.0/backend/start.sh":{"size":9548,"mtime_ns":1780114661446983244,"hash":"4d632863e8a0cd81194b491d83a0218a4157435ae812dec76a6f6197b4d48e57"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/__init__.py":{"size":21,"mtime_ns":1780113982281973984,"hash":"60a04ff08fd97084e83ea809fd13feda6d2848be20ebf309e31fc1dad7523230"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/main.py":{"size":6280,"mtime_ns":1782790852770448643,"hash":"8f9da799977bee7134d87a26ff8434ac5ef8eabb0b7368dd537eee49ad7fd00b"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/tasks/__init__.py":{"size":2207,"mtime_ns":1783208067881286091,"hash":"313e6da0e91d743ce891dd283458c79b50b24c4d909dc96ca7b3fd9d2bfa5013"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/auth.py":{"size":7457,"mtime_ns":1782790841500516545,"hash":"34bac56e1c2cb6215f4b5fa2dc32d77d4b4abca51428819eebf4afd269cc51cf"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_crypto.py":{"size":1870,"mtime_ns":1780108930724444498,"hash":"c04b748aea86890a5971f749523c7b17dbad1ddd6f9f7f07a5679d159c984e33"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/login_crypto.py":{"size":3858,"mtime_ns":1780108930725155710,"hash":"b82d51afabf2293a9cb1fb2e5f994c383948e7f47ea95fe674686b4f3f3342b1"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/config.py":{"size":6716,"mtime_ns":1783761682571576393,"hash":"bbe64081c9790941eb2fa8f9c18ee39daf151068b3503f1fdb74c2cb9f7b5b45"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/wechat_pay.py":{"size":23585,"mtime_ns":1780108930725768130,"hash":"3d52265ce65d21e7103f94afa1e8547aee0823b5ea6d65c50317436ac81fb4fc"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_request_log_middleware.py":{"size":11971,"mtime_ns":1782701669198208175,"hash":"0b84c32828939e6102bb3647691fa8f04b1f68e0e0e9f28127374cc44e19a11c"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/database.py":{"size":1510,"mtime_ns":1782790847265601586,"hash":"8bc9c8ae62e2be2c6920036582ba448dfcd1711c41cebb62274ff6bc6ee14b0b"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/security.py":{"size":728,"mtime_ns":1780108930725470004,"hash":"df0f01115064f7b8de1c81dbe9a6db39360c236610b68c33b3234a51d7f0025b"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/request_payload.py":{"size":2842,"mtime_ns":1780108930725350836,"hash":"e9937151530ab1417ef297aef59111977725cf90be2a8fa76c00a0618789aba0"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/__init__.py":{"size":61,"mtime_ns":1780108930724312414,"hash":"3dd1842ee59b0c804176b03b4b8a323bd4a252b7feb1bbc32db64f3a8928570e"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/response.py":{"size":1824,"mtime_ns":1780108930725412462,"hash":"f1739352d65ea8287d76e282a59553aa8d63c97e35764c64ac378aa5bd9c347e"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/menu_guard.py":{"size":1611,"mtime_ns":1780108930725223002,"hash":"3e26077288c3620a4780038b891a326206b45bffe00aca545bdae30172487235"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/db_collation.py":{"size":548,"mtime_ns":1780108930725025043,"hash":"0c680e25a18244ca839a7de6ed1f4313b33bc8da118d89c463aee1b6af00c6f9"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/context.py":{"size":421,"mtime_ns":1780108930724824458,"hash":"83a10aee67162374993c10a30bf8bb8bd9c4b35cf48f42c832415ccebd91660f"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/utils.py":{"size":1498,"mtime_ns":1780108930725607296,"hash":"09323693661c1d4560458b86a954128ccff82bbaf6522da0b44f24da99ddf9ea"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/storage.py":{"size":14141,"mtime_ns":1782855136810943216,"hash":"3ae2c413c10898a8adb3d6773870bafbfbc18c739c10e0e8b4f765b4b5ca6c08"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/order_status_compat.py":{"size":2828,"mtime_ns":1780108930725284669,"hash":"5349e5a0745ee6734c4a995337779307adefc46f453906171988a77de2081540"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/exceptions.py":{"size":4946,"mtime_ns":1780108930725084335,"hash":"79c997d964bca1a4f12f2b92fb1f181fdb9e2b3cf72eae39f4cc164d23794fd7"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/data_scope.py":{"size":8043,"mtime_ns":1782648925694296304,"hash":"626dff86937e5cacec634ecbc61cd53d3763d760223a1d4ec7cd11f64e59a220"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/admin_request_json.py":{"size":2669,"mtime_ns":1780108930724378914,"hash":"4f68b3cf84291e7bddad6ecf4bb4d29ca5e1ec246a9f7f9c4cf5909d0ec1174a"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/api_crypto_middleware.py":{"size":10876,"mtime_ns":1782790001926627098,"hash":"b39f128b6612589ea6ac5babd30752e9585b3af07b82bb1ad53c9e3c0d90816f"},"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/wechat_message.py":{"size":11194,"mtime_ns":1780108930725684255,"hash":"5086070deb14d99d1a69e419b2299ec4Line truncated
|
||||
+5883
-3195
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,136 @@
|
||||
"""地磅月计划量运输联动累加/回减单元测试"""
|
||||
from decimal import Decimal
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from app.models.db_models import DbInboundOutbound, DbPurchasePlan
|
||||
from app.models.pl_models import PlTransportDbLink
|
||||
from app.services.pl_dibang_bridge_service import (
|
||||
_apply_plan_quantity_delta,
|
||||
_resolve_link_contributed_qty,
|
||||
revoke_regeneratable_dibang_for_transport,
|
||||
)
|
||||
|
||||
|
||||
def test_apply_plan_quantity_delta_accumulates():
|
||||
db_plan = DbPurchasePlan(planned_quantity=30.0)
|
||||
_apply_plan_quantity_delta(db_plan, 31, user_id="u1")
|
||||
assert db_plan.planned_quantity == 61.0
|
||||
assert db_plan.update_by == "u1"
|
||||
|
||||
|
||||
def test_apply_plan_quantity_delta_never_below_zero():
|
||||
db_plan = DbPurchasePlan(planned_quantity=30.0)
|
||||
_apply_plan_quantity_delta(db_plan, -50, user_id="u1")
|
||||
assert db_plan.planned_quantity == 0.0
|
||||
|
||||
|
||||
def test_resolve_link_contributed_qty_prefers_link_field():
|
||||
link = PlTransportDbLink(contributed_qty=Decimal("30"))
|
||||
inventory = DbInboundOutbound(quantity=Decimal("99"))
|
||||
assert _resolve_link_contributed_qty(link, inventory) == 30.0
|
||||
|
||||
|
||||
def test_resolve_link_contributed_qty_falls_back_to_inventory():
|
||||
link = PlTransportDbLink(contributed_qty=None)
|
||||
inventory = DbInboundOutbound(quantity=Decimal("31"))
|
||||
assert _resolve_link_contributed_qty(link, inventory) == 31.0
|
||||
|
||||
|
||||
def test_resolve_link_contributed_qty_zero_when_missing():
|
||||
link = PlTransportDbLink(contributed_qty=None)
|
||||
assert _resolve_link_contributed_qty(link, None) == 0.0
|
||||
|
||||
|
||||
|
||||
@patch("app.services.pl_dibang_bridge_service._inventory_has_weighing_slip", return_value=False)
|
||||
def test_revoke_subtracts_plan_quantity_on_cancel(_mock_weighing):
|
||||
db_plan = DbPurchasePlan(id="plan-1", tenant_no="t1", planned_quantity=61.0, del_flag=0)
|
||||
inventory = DbInboundOutbound(
|
||||
id="inv-1",
|
||||
tenant_no="t1",
|
||||
del_flag=0,
|
||||
status="待入库",
|
||||
quantity=Decimal("30"),
|
||||
batch_no="CRKDH0000001",
|
||||
)
|
||||
link = PlTransportDbLink(
|
||||
id="link-1",
|
||||
tenant_no="t1",
|
||||
transport_id="tr-1",
|
||||
db_inventory_id="inv-1",
|
||||
db_purchase_plan_id="plan-1",
|
||||
contributed_qty=Decimal("30"),
|
||||
del_flag=0,
|
||||
)
|
||||
|
||||
db = MagicMock()
|
||||
link_query = MagicMock()
|
||||
link_query.filter.return_value.all.return_value = [link]
|
||||
|
||||
inv_query = MagicMock()
|
||||
inv_query.filter.return_value.first.return_value = inventory
|
||||
|
||||
plan_query = MagicMock()
|
||||
plan_query.filter.return_value.first.return_value = db_plan
|
||||
|
||||
def query_router(model):
|
||||
if model is PlTransportDbLink:
|
||||
return link_query
|
||||
if model is DbInboundOutbound:
|
||||
return inv_query
|
||||
if model is DbPurchasePlan:
|
||||
return plan_query
|
||||
return MagicMock()
|
||||
|
||||
db.query.side_effect = query_router
|
||||
|
||||
revoke_regeneratable_dibang_for_transport(
|
||||
db,
|
||||
tenant_no="t1",
|
||||
transport_id="tr-1",
|
||||
user_id="u1",
|
||||
)
|
||||
|
||||
assert db_plan.planned_quantity == 31.0
|
||||
assert inventory.del_flag == 1
|
||||
assert link.del_flag == 1
|
||||
|
||||
|
||||
@patch("app.services.pl_dibang_bridge_service._inventory_has_weighing_slip", return_value=True)
|
||||
def test_revoke_blocks_when_weighing_exists(_mock_weighing):
|
||||
inventory = DbInboundOutbound(
|
||||
id="inv-1",
|
||||
tenant_no="t1",
|
||||
del_flag=0,
|
||||
status="待入库",
|
||||
batch_no="CRKDH0000001",
|
||||
)
|
||||
link = PlTransportDbLink(
|
||||
id="link-1",
|
||||
tenant_no="t1",
|
||||
transport_id="tr-1",
|
||||
db_inventory_id="inv-1",
|
||||
db_purchase_plan_id="plan-1",
|
||||
contributed_qty=Decimal("30"),
|
||||
del_flag=0,
|
||||
)
|
||||
|
||||
db = MagicMock()
|
||||
link_query = MagicMock()
|
||||
link_query.filter.return_value.all.return_value = [link]
|
||||
inv_query = MagicMock()
|
||||
inv_query.filter.return_value.first.return_value = inventory
|
||||
db.query.side_effect = lambda model: link_query if model is PlTransportDbLink else inv_query
|
||||
|
||||
with pytest.raises(ValueError, match="已关联地磅"):
|
||||
revoke_regeneratable_dibang_for_transport(
|
||||
db,
|
||||
tenant_no="t1",
|
||||
transport_id="tr-1",
|
||||
user_id="u1",
|
||||
)
|
||||
|
||||
assert inventory.del_flag == 0
|
||||
assert link.del_flag == 0
|
||||
@@ -0,0 +1,3 @@
|
||||
-- 运输联动贡献量(吨),用于地磅月计划量累加/回减
|
||||
ALTER TABLE pl_transport_db_link
|
||||
ADD COLUMN contributed_qty DECIMAL(15,3) NULL COMMENT '本次联动贡献量(吨)' AFTER db_inventory_id;
|
||||
@@ -0,0 +1,19 @@
|
||||
-- 修复商机 follow_count 与跟进记录数不一致(历史数据回填)
|
||||
-- 原因:创建跟进时 recalc 发生在 flush 之前,导致冗余字段长期少 1
|
||||
|
||||
UPDATE crm_opportunity o
|
||||
LEFT JOIN (
|
||||
SELECT opportunity_id, COUNT(*) AS cnt
|
||||
FROM crm_opportunity_follow
|
||||
WHERE del_flag = 0
|
||||
GROUP BY opportunity_id
|
||||
) f ON f.opportunity_id = o.id
|
||||
SET o.follow_count = IFNULL(f.cnt, 0),
|
||||
o.last_time = (
|
||||
SELECT MAX(COALESCE(f2.follow_time, f2.create_time))
|
||||
FROM crm_opportunity_follow f2
|
||||
WHERE f2.opportunity_id = o.id
|
||||
AND f2.del_flag = 0
|
||||
)
|
||||
WHERE o.del_flag = 0
|
||||
AND IFNULL(o.follow_count, 0) <> IFNULL(f.cnt, 0);
|
||||
@@ -356,6 +356,7 @@ CREATE TABLE `pl_transport_db_link` (
|
||||
`plan_product_id` varchar(64) NOT NULL COMMENT '计划产品行ID',
|
||||
`db_purchase_plan_id` varchar(64) NOT NULL COMMENT '地磅计划ID',
|
||||
`db_inventory_id` varchar(64) NOT NULL COMMENT '出入库单ID',
|
||||
`contributed_qty` decimal(15,3) DEFAULT NULL COMMENT '本次联动贡献量(吨)',
|
||||
`tenant_no` varchar(64) NOT NULL DEFAULT 'shanxia2.0' COMMENT '租户编号',
|
||||
`order_num` int(11) DEFAULT NULL COMMENT '排序号',
|
||||
`remark` varchar(512) DEFAULT NULL COMMENT '备注',
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
# 地磅月计划量运输联动累加与回减 Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 运输计划勾选「生成地磅单」时,地磅月计划量按产品行累加;调整运输与取消计划作废旧联动时同步回减贡献量。
|
||||
|
||||
**Architecture:** 在 `pl_transport_db_link` 记录 `contributed_qty`;`revoke_regeneratable_dibang_for_transport` 统一回减;`_sync_transport_inventory_links` 统一累加。取消计划与调整运输复用现有 revoke 入口,无需新增 API。
|
||||
|
||||
**Tech Stack:** FastAPI + SQLAlchemy + pytest;MySQL 5.7
|
||||
|
||||
**OpenSpec:** `openspec/changes/pl-dibang-plan-quantity-accumulation/`
|
||||
**Design:** `docs/superpowers/specs/2026-07-11-pl-dibang-plan-quantity-accumulation-design.md`
|
||||
|
||||
---
|
||||
|
||||
## 文件结构
|
||||
|
||||
| 操作 | 路径 | 职责 |
|
||||
|------|------|------|
|
||||
| Create | `docs/sql/20260711_pl_transport_db_link_contributed_qty.sql` | 增量 DDL |
|
||||
| Modify | `docs/sql/pl_tables.sql` | 基线同步 `contributed_qty` |
|
||||
| Modify | `backend/app/models/pl_models.py` | ORM `contributed_qty` |
|
||||
| Modify | `backend/app/services/pl_dibang_bridge_service.py` | 累加/回减逻辑 |
|
||||
| Create | `backend/tests/test_pl_dibang_plan_quantity.py` | 单元测试 |
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 数据库与 ORM
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/sql/20260711_pl_transport_db_link_contributed_qty.sql`
|
||||
- Modify: `docs/sql/pl_tables.sql`
|
||||
- Modify: `backend/app/models/pl_models.py`
|
||||
|
||||
- [ ] **Step 1: 编写增量 SQL**
|
||||
|
||||
```sql
|
||||
-- 运输联动贡献量(吨),用于月计划量累加/回减
|
||||
ALTER TABLE pl_transport_db_link
|
||||
ADD COLUMN contributed_qty DECIMAL(15,3) NULL COMMENT '本次联动贡献量(吨)' AFTER db_inventory_id;
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 更新 `pl_tables.sql`**
|
||||
|
||||
在 `pl_transport_db_link` 的 `db_inventory_id` 后增加:
|
||||
|
||||
```sql
|
||||
`contributed_qty` decimal(15,3) DEFAULT NULL COMMENT '本次联动贡献量(吨)',
|
||||
```
|
||||
|
||||
- [ ] **Step 3: ORM 字段**
|
||||
|
||||
在 `PlTransportDbLink` 增加:
|
||||
|
||||
```python
|
||||
contributed_qty = Column(DECIMAL(15, 3), nullable=True, comment="本次联动贡献量(吨)")
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 本地执行 DDL**(开发库)
|
||||
|
||||
```bash
|
||||
mysql -u ... -p ... < docs/sql/20260711_pl_transport_db_link_contributed_qty.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: 桥接服务 — 辅助函数
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/services/pl_dibang_bridge_service.py`
|
||||
|
||||
- [ ] **Step 1: 新增 `_apply_plan_quantity_delta`**
|
||||
|
||||
```python
|
||||
def _apply_plan_quantity_delta(
|
||||
db_plan: DbPurchasePlan,
|
||||
delta: float,
|
||||
*,
|
||||
user_id: Optional[str],
|
||||
) -> None:
|
||||
current = float(db_plan.planned_quantity or 0)
|
||||
db_plan.planned_quantity = max(0.0, current + float(delta))
|
||||
db_plan.update_by = user_id
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 新增 `_resolve_link_contributed_qty`**
|
||||
|
||||
```python
|
||||
def _resolve_link_contributed_qty(
|
||||
link: PlTransportDbLink,
|
||||
inventory: Optional[DbInboundOutbound],
|
||||
) -> float:
|
||||
if link.contributed_qty is not None:
|
||||
return float(link.contributed_qty)
|
||||
if inventory and inventory.quantity is not None:
|
||||
return float(inventory.quantity)
|
||||
return 0.0
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 新增 `_subtract_plan_contribution`**
|
||||
|
||||
在作废每条 link 前调用:
|
||||
|
||||
```python
|
||||
def _subtract_plan_contribution(
|
||||
db: Session,
|
||||
*,
|
||||
tenant_no: str,
|
||||
link: PlTransportDbLink,
|
||||
inventory: Optional[DbInboundOutbound],
|
||||
user_id: Optional[str],
|
||||
) -> None:
|
||||
if not link.db_purchase_plan_id:
|
||||
return
|
||||
db_plan = (
|
||||
db.query(DbPurchasePlan)
|
||||
.filter(
|
||||
DbPurchasePlan.id == link.db_purchase_plan_id,
|
||||
DbPurchasePlan.tenant_no == tenant_no,
|
||||
DbPurchasePlan.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not db_plan:
|
||||
return
|
||||
qty = _resolve_link_contributed_qty(link, inventory)
|
||||
if qty:
|
||||
_apply_plan_quantity_delta(db_plan, -qty, user_id=user_id)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 桥接服务 — 累加(sync)
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/services/pl_dibang_bridge_service.py`
|
||||
|
||||
- [ ] **Step 1: 在 `_sync_transport_inventory_links` 产品循环内,取得 `contrib`**
|
||||
|
||||
```python
|
||||
contrib = _product_quantity(product)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 复用月计划分支增加累加**
|
||||
|
||||
在 `if not db_plan: ... _create_db_plan(...)` 的 `else` 分支(或 `db_plan` 已存在时):
|
||||
|
||||
```python
|
||||
_apply_plan_quantity_delta(db_plan, contrib, user_id=user_id)
|
||||
```
|
||||
|
||||
注意:`_create_db_plan` 新建时 `planned_quantity` 已设为 `contrib`,**仅复用**时执行 `+=`。
|
||||
|
||||
- [ ] **Step 3: 创建 link 时写入 `contributed_qty`**
|
||||
|
||||
```python
|
||||
link = PlTransportDbLink(
|
||||
...
|
||||
contributed_qty=Decimal(str(contrib)),
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 桥接服务 — 回减(revoke)
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/services/pl_dibang_bridge_service.py`
|
||||
|
||||
- [ ] **Step 1: 在 `revoke_regeneratable_dibang_for_transport` 第二段循环(实际作废)中,软删前调用回减**
|
||||
|
||||
在 `if inventory: inventory.del_flag = 1` **之前**:
|
||||
|
||||
```python
|
||||
_subtract_plan_contribution(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
link=link,
|
||||
inventory=inventory,
|
||||
user_id=user_id,
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 确认 `cancel_plan` 与 `arrange_transport` 调整分支无需改入口**(已调用 revoke)
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 单元测试
|
||||
|
||||
**Files:**
|
||||
- Create: `backend/tests/test_pl_dibang_plan_quantity.py`
|
||||
|
||||
- [ ] **Step 1: 编写测试(使用项目现有 pytest + DB session 夹具模式;若无夹具则用 mock Session 测纯函数)**
|
||||
|
||||
至少覆盖:
|
||||
|
||||
```python
|
||||
def test_apply_plan_quantity_delta_never_below_zero():
|
||||
...
|
||||
|
||||
def test_sync_creates_plan_with_contributed_qty():
|
||||
# 无月计划 → planned_quantity = 30, link.contributed_qty = 30
|
||||
...
|
||||
|
||||
def test_sync_reuses_plan_and_accumulates():
|
||||
# 已有 30,再 +31 → 61
|
||||
...
|
||||
|
||||
def test_revoke_subtracts_on_cancel():
|
||||
# 61 - 30 → 31
|
||||
...
|
||||
|
||||
def test_revoke_then_sync_on_rearrange():
|
||||
# 30 回减后 +25 → 25
|
||||
...
|
||||
|
||||
def test_revoke_blocks_when_weighing_exists():
|
||||
# 月计划量不变
|
||||
...
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行测试**
|
||||
|
||||
```bash
|
||||
cd backend && source venv/bin/activate && pytest tests/test_pl_dibang_plan_quantity.py -v
|
||||
```
|
||||
|
||||
Expected: 全部 PASS
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 图谱与验收
|
||||
|
||||
- [ ] **Step 1: 更新知识图谱**
|
||||
|
||||
```bash
|
||||
./scripts/update-graph.sh backend
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 手工验收清单**
|
||||
|
||||
| 步骤 | 操作 | 预期月计划量 |
|
||||
|------|------|-------------|
|
||||
| 1 | 计划 A 安排,B+C,30t,勾地磅 | 30 |
|
||||
| 2 | 计划 B 安排,B+C,31t,勾地磅 | 61 |
|
||||
| 3 | 取消计划 A | 31 |
|
||||
| 4 | 计划 B 调整 31→25,勾地磅 | 25 |
|
||||
| 5 | 对已完成/已关联磅单单据尝试取消 | 失败,量不变 |
|
||||
|
||||
---
|
||||
|
||||
## Spec 覆盖自检
|
||||
|
||||
| OpenSpec Scenario | Task |
|
||||
|-------------------|------|
|
||||
| 首次联动创建月计划 | Task 3 |
|
||||
| 后续联动累加月计划 | Task 3 |
|
||||
| 调整运输变更数量 | Task 4 + Task 3 |
|
||||
| 调整运输取消勾选地磅 | Task 4 |
|
||||
| 调整运输被阻断 | Task 4(现有门禁不变) |
|
||||
| 取消计划回减月计划量 | Task 4 |
|
||||
| 取消计划被阻断 | Task 4 |
|
||||
| 取消后月计划量下限 | Task 2 `_apply_plan_quantity_delta` |
|
||||
|
||||
---
|
||||
|
||||
## 执行方式
|
||||
|
||||
Plan 已保存至 `docs/superpowers/plans/2026-07-11-pl-dibang-plan-quantity-accumulation.md`。
|
||||
|
||||
**1. Subagent-Driven(推荐)** — 每 Task 派发子代理,逐 Task 评审
|
||||
**2. Inline Execution** — 本会话按 Task 顺序直接改代码
|
||||
|
||||
需要我开始实现吗?
|
||||
@@ -0,0 +1,37 @@
|
||||
# 地磅月计划量运输联动累加与回减 — 设计文档
|
||||
|
||||
> **日期**: 2026-07-11
|
||||
> **状态**: 待评审
|
||||
> **OpenSpec**: `openspec/changes/pl-dibang-plan-quantity-accumulation/`
|
||||
> **前置**: `docs/superpowers/specs/2026-06-20-pl-arrange-dibang-design.md`
|
||||
|
||||
## 背景
|
||||
|
||||
用户期望:同月同往来单位、同产品的多次运输计划勾选「生成地磅单」时,地磅月计划量应累加;取消或调整运输时应回减已贡献的量。
|
||||
|
||||
## 已确认决策
|
||||
|
||||
| # | 决策 |
|
||||
|---|------|
|
||||
| D1 | 用 `pl_transport_db_link.contributed_qty` 记录单次贡献(吨) |
|
||||
| D2 | 回减集中在 `revoke_regeneratable_dibang_for_transport`(覆盖调整运输 + 取消计划) |
|
||||
| D3 | 累加在 `_sync_transport_inventory_links`(新建或复用月计划后 `+=`) |
|
||||
| D4 | `planned_quantity` 回减下限为 0,不删除月计划行 |
|
||||
| D5 | 已关联磅单/不可作废出入库单时整单阻断,月计划量不变 |
|
||||
|
||||
## 验收场景(摘自 OpenSpec)
|
||||
|
||||
| # | 场景 | 预期 |
|
||||
|---|------|------|
|
||||
| V1 | A(30) 首次联动 | 月计划量 = 30 |
|
||||
| V2 | B(31) 同维度再联动 | 月计划量 = 61 |
|
||||
| V3 | 取消 A | 月计划量 = 31 |
|
||||
| V4 | 调整 A 30→25 | 月计划量 = 25 |
|
||||
| V5 | 调整不勾地磅 | 月计划量回减,不新建联动 |
|
||||
| V6 | 已关联磅单时取消/调整 | 操作失败,月计划量不变 |
|
||||
|
||||
## 参考实现位置
|
||||
|
||||
- `backend/app/services/pl_dibang_bridge_service.py`
|
||||
- `backend/app/api/pl/transport.py` → `arrange_transport`
|
||||
- `backend/app/api/pl/plan.py` → `cancel_plan`
|
||||
+66
-61
@@ -60,8 +60,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/config.py": {
|
||||
"mtime": 1782790876.685113,
|
||||
"ast_hash": "fdb7d2c4aadb0c0ad3f36f003a881896",
|
||||
"mtime": 1783761682.5715764,
|
||||
"ast_hash": "770866e1055881cf96183a1c31027d43",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/core/wechat_pay.py": {
|
||||
@@ -210,8 +210,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/models/pl_models.py": {
|
||||
"mtime": 1783253795.1009343,
|
||||
"ast_hash": "a0804629d38dcca19b2001cf91ec0673",
|
||||
"mtime": 1783777743.341288,
|
||||
"ast_hash": "c922ece8f7295870b9b64f61ee6ddae9",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/models/models.py": {
|
||||
@@ -430,13 +430,13 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/product.py": {
|
||||
"mtime": 1782853111.9225569,
|
||||
"ast_hash": "2ed01611fcb3ad2c1dde82506fb74439",
|
||||
"mtime": 1783820526.2041585,
|
||||
"ast_hash": "40fb32a388517af9a50e6f2906d39c74",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/customer.py": {
|
||||
"mtime": 1780713560.8434699,
|
||||
"ast_hash": "639b5ea6917acab50233b93149d96f3b",
|
||||
"mtime": 1783823672.041317,
|
||||
"ast_hash": "39e0e4d75f5e6d344f495bd1de13083e",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/statistics.py": {
|
||||
@@ -445,7 +445,7 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/schemas/crm/contract.py": {
|
||||
"mtime": 1783760882.0429533,
|
||||
"mtime": 1783823649.766528,
|
||||
"ast_hash": "6553f54ca1fd19086c170095f402a628",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
@@ -610,8 +610,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/opportunity_follow.py": {
|
||||
"mtime": 1782687669.5521226,
|
||||
"ast_hash": "74c8c9a39659b8b97adc1b9751b59753",
|
||||
"mtime": 1783821986.0404174,
|
||||
"ast_hash": "1a340f1428e7208373ca4ae545e97c56",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/quote.py": {
|
||||
@@ -680,8 +680,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/product.py": {
|
||||
"mtime": 1782853124.0849667,
|
||||
"ast_hash": "c12e0b89c7915836a9c1eb49ca63ce5c",
|
||||
"mtime": 1783820522.540309,
|
||||
"ast_hash": "6ff985e8b6c4bc8518976f1a7f5ef63f",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/customer.py": {
|
||||
@@ -695,8 +695,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contract.py": {
|
||||
"mtime": 1782687678.1298795,
|
||||
"ast_hash": "3a03a88e2332b2cf4a88976a191e6583",
|
||||
"mtime": 1783763318.7403605,
|
||||
"ast_hash": "5ff35e415f26e1159e79395538aade07",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/crud/crm/contact.py": {
|
||||
@@ -710,8 +710,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/api/public.py": {
|
||||
"mtime": 1782790854.8675761,
|
||||
"ast_hash": "352bfde90d23fdff82eb2189ab058fea",
|
||||
"mtime": 1783761683.8630195,
|
||||
"ast_hash": "aefd2bb5fdb09571cd33fa133d12ca55",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/api/monitor.py": {
|
||||
@@ -725,8 +725,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm.py": {
|
||||
"mtime": 1782853126.2804675,
|
||||
"ast_hash": "006092a244ecff23fd16cbf142e69437",
|
||||
"mtime": 1783819836.5052142,
|
||||
"ast_hash": "185d877b19159d9d996122fea0bb4f17",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/api/__init__.py": {
|
||||
@@ -740,8 +740,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_quote_routes.py": {
|
||||
"mtime": 1782650290.4937038,
|
||||
"ast_hash": "d629c27a20d22a3d731cda0dbec0d6b4",
|
||||
"mtime": 1783825326.8107653,
|
||||
"ast_hash": "8f42ad854382af58e5ed9b50192c66a8",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/api/admin.py": {
|
||||
@@ -750,8 +750,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/api/crm_contract_routes.py": {
|
||||
"mtime": 1782699376.518852,
|
||||
"ast_hash": "90194b08a79a8eec2bdf17de6b62d9fe",
|
||||
"mtime": 1783763319.0708206,
|
||||
"ast_hash": "44223920a99b6b0796a49232e8c93a8e",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/api/workflow.py": {
|
||||
@@ -905,8 +905,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py": {
|
||||
"mtime": 1783754600.6087332,
|
||||
"ast_hash": "377caea561fa9566945d26f8867c0d17",
|
||||
"mtime": 1783768766.0299537,
|
||||
"ast_hash": "8dc07a801eaf0e590cd8dc3c2e0a0fc3",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/services/workflow_engine.py": {
|
||||
@@ -925,8 +925,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_dibang_bridge_service.py": {
|
||||
"mtime": 1782227851.746783,
|
||||
"ast_hash": "9f89467e28bdd7afb685442d68b62d40",
|
||||
"mtime": 1783777749.4408944,
|
||||
"ast_hash": "4b28a02e47270ec9d6d563abd6eb2626",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_vehicle_urea_service.py": {
|
||||
@@ -935,8 +935,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/services/web_auth_roles.py": {
|
||||
"mtime": 1780201410.114958,
|
||||
"ast_hash": "76817c400c5eb1caf47ebe6aa4ab01a4",
|
||||
"mtime": 1783761681.6072063,
|
||||
"ast_hash": "e9e8c0bde894350b65cff1aa3e4b3abd",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/app/services/pl_freight_settlement_service.py": {
|
||||
@@ -2460,8 +2460,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/composables/usePlToxicProductCertCheck.ts": {
|
||||
"mtime": 1783751322.1412632,
|
||||
"ast_hash": "83594f6faf7468199fb530a7cd29942c",
|
||||
"mtime": 1783768767.5174649,
|
||||
"ast_hash": "e9bc2c8ed7e5d39fbbb850bdf31a45ff",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/locales/index.ts": {
|
||||
@@ -2955,8 +2955,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/components/CustomerSelector.vue": {
|
||||
"mtime": 1782858531.0552773,
|
||||
"ast_hash": "98771e4601b92a345402756d0f2bf9e1",
|
||||
"mtime": 1783824595.3424296,
|
||||
"ast_hash": "9ba7f888219ec2e8a26eb9cc6d9b6d27",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/components/OpportunitySelector.vue": {
|
||||
@@ -3600,8 +3600,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/components/business/attachment-upload-field/index.vue": {
|
||||
"mtime": 1783202853.2057269,
|
||||
"ast_hash": "c8ee3840407e179852078e0e4553c865",
|
||||
"mtime": 1783823902.3416996,
|
||||
"ast_hash": "35a5dd3288c74ae0d70f85ec8bbad7db",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/components/business/pl-freight-approval-dialog/index.vue": {
|
||||
@@ -3720,8 +3720,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/components/business/pl-plan-arrange-dialog/index.vue": {
|
||||
"mtime": 1783754107.3458776,
|
||||
"ast_hash": "a12ea444a2a64e098d24ffe2a6ea6904",
|
||||
"mtime": 1783769816.3338885,
|
||||
"ast_hash": "2884ad0b2e6e897f084b283f3d03c993",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/components/business/contract-selector/index.vue": {
|
||||
@@ -4295,8 +4295,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/pl/carrier/index.vue": {
|
||||
"mtime": 1783176825.8335733,
|
||||
"ast_hash": "f7db9387cf0c1f529ad1f3b26c34039b",
|
||||
"mtime": 1783821190.6414018,
|
||||
"ast_hash": "047b4cc96dc747d17f802d892ba4f424",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/pl/personnel/index.vue": {
|
||||
@@ -4385,8 +4385,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/clue/detailDrawer.vue": {
|
||||
"mtime": 1783753619.656547,
|
||||
"ast_hash": "48cba2f8a723c016ebd5eed3c2110715",
|
||||
"mtime": 1783821704.5264707,
|
||||
"ast_hash": "5d701a69b6e84bc4ae55fd343df8fec5",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/clue/convertForm.vue": {
|
||||
@@ -4400,8 +4400,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/clue/followForm.vue": {
|
||||
"mtime": 1783167842.1795158,
|
||||
"ast_hash": "af1803baa6eb8cae5af763ab8c9ddc48",
|
||||
"mtime": 1783820990.01577,
|
||||
"ast_hash": "c771ed36b518d5446110277d71b52440",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/clue/detail.vue": {
|
||||
@@ -4450,8 +4450,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/opportunity/components/FollowDialog.vue": {
|
||||
"mtime": 1783167842.0515602,
|
||||
"ast_hash": "a4de29a9699bf3a7608a1fec2962ffc6",
|
||||
"mtime": 1783821190.7793255,
|
||||
"ast_hash": "66af79b6f89974751fbc7dd974cd3bd6",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/opportunity/components/BatchUpdateStageDialog.vue": {
|
||||
@@ -4560,8 +4560,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/quote/index.vue": {
|
||||
"mtime": 1783753615.4545014,
|
||||
"ast_hash": "94a65c4ccb9fe9e3e041e7c41525ab70",
|
||||
"mtime": 1783825109.3807256,
|
||||
"ast_hash": "fa9a6e35d8e03d26f7a87176918b8735",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/quote/statistics.vue": {
|
||||
@@ -4590,8 +4590,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/quote/components/QuoteDetailDialog.vue": {
|
||||
"mtime": 1782858541.3595047,
|
||||
"ast_hash": "50b376b69b6db7ab1b205d1293c8618b",
|
||||
"mtime": 1783825601.5186715,
|
||||
"ast_hash": "d58a617eedcb033280f9a3fd5b533252",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/quote/components/VersionHistoryDialog.vue": {
|
||||
@@ -4605,8 +4605,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/quote/components/QuoteFormPanel.vue": {
|
||||
"mtime": 1783167832.2729018,
|
||||
"ast_hash": "ed483352cff8b803943da7f79268c398",
|
||||
"mtime": 1783824505.3333218,
|
||||
"ast_hash": "affd16b36a130f3812e5bb2133747a7f",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/quote/components/ShareDialog.vue": {
|
||||
@@ -4635,8 +4635,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/product/index.vue": {
|
||||
"mtime": 1783173429.157619,
|
||||
"ast_hash": "458f348b8e1c5ea5d8703524b30db38b",
|
||||
"mtime": 1783821190.641404,
|
||||
"ast_hash": "a008811667abe7b2d68fa937e4fa2c56",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/customer/index.vue": {
|
||||
@@ -4645,8 +4645,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/customer/FollowFormDialog.vue": {
|
||||
"mtime": 1783167838.3380034,
|
||||
"ast_hash": "43e2fbd04e833b0e8ee58336f6eda942",
|
||||
"mtime": 1783821191.1033955,
|
||||
"ast_hash": "8c88f603ed5275aefccc8a40c62a8794",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/customer/TransferDialog.vue": {
|
||||
@@ -4655,8 +4655,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/customer/CustomerDetailDialog.vue": {
|
||||
"mtime": 1783171251.6549275,
|
||||
"ast_hash": "c9daeb848a9d04097221452225be8180",
|
||||
"mtime": 1783823911.7927911,
|
||||
"ast_hash": "291b100b9b5ec6f6bd8c6602434265b7",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/customer/DealDialog.vue": {
|
||||
@@ -4665,8 +4665,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/customer/CustomerFormDialog.vue": {
|
||||
"mtime": 1783167305.9037223,
|
||||
"ast_hash": "8369e92c0364a12bd3ef232f496ee02d",
|
||||
"mtime": 1783823672.333586,
|
||||
"ast_hash": "0909afcc068c2bc42985e6e37837d176",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/views/crm/customer/IntoPoolDialog.vue": {
|
||||
@@ -5010,8 +5010,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/router/guards/beforeEach.ts": {
|
||||
"mtime": 1783257592.081401,
|
||||
"ast_hash": "c8dd5356651e9faa03e9247acde72322",
|
||||
"mtime": 1783761696.732008,
|
||||
"ast_hash": "ed40e0b8a735e7b96b4993b1f2a461c8",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/web-admin/src/router/guards/afterEach.ts": {
|
||||
@@ -7393,5 +7393,10 @@
|
||||
"mtime": 1783760394.5188968,
|
||||
"ast_hash": "2f8542187bc2678c3a779259a43288ee",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"/Users/zlwu/work/00code/shanxia2.0/backend/tests/test_pl_dibang_plan_quantity.py": {
|
||||
"mtime": 1783777759.8294604,
|
||||
"ast_hash": "73869416708f10036beec5769af334bb",
|
||||
"semantic_hash": ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-07-11
|
||||
@@ -0,0 +1,69 @@
|
||||
# 地磅月计划量运输联动累加与回减 — 设计
|
||||
|
||||
> **日期**: 2026-07-11
|
||||
> **OpenSpec**: `openspec/changes/pl-dibang-plan-quantity-accumulation/`
|
||||
> **前置**: `docs/superpowers/specs/2026-06-20-pl-arrange-dibang-design.md`
|
||||
|
||||
## 目标
|
||||
|
||||
当月同维度(租户 + 业务类型 + CRM 产品 + 规格 + 往来单位)的地磅月计划,`planned_quantity` 应等于所有**仍有效**的运输地磅联动贡献之和;取消或调整运输作废联动时同步回减。
|
||||
|
||||
## 核心决策
|
||||
|
||||
| # | 决策 | 说明 |
|
||||
|---|------|------|
|
||||
| D1 | **贡献量记账** | `pl_transport_db_link.contributed_qty`(吨)为单次联动写入量;回减以该字段为准 |
|
||||
| D2 | **贡献量来源** | 创建 link 时取 `_product_quantity(product)`,优先级:`quantity` → `inbound_estimate_qty` → `outbound_estimate_qty` → `0` |
|
||||
| D3 | **累加时机** | `_sync_transport_inventory_links` 在创建/复用月计划后 `+= contributed_qty` |
|
||||
| D4 | **回减时机** | `revoke_regeneratable_dibang_for_transport` 在软删 link 前 `-= contributed_qty`;`planned_quantity = max(0, value)` |
|
||||
| D5 | **调整运输** | 复用 arrange 流程:先 revoke(回减)→ 再 sync(累加新贡献);净效果等同「先减旧再加新」 |
|
||||
| D6 | **取消计划** | `cancel_plan` 已调用 revoke;回减逻辑集中在 revoke,cancel 无需改入口 |
|
||||
| D7 | **阻断不变** | 出入库单已关联磅单或状态不可作废时,整单阻断;月计划量不变 |
|
||||
| D8 | **月计划保留** | 回减至 0 不删除 `db_purchase_plan` 行 |
|
||||
|
||||
## 数据流
|
||||
|
||||
```
|
||||
安排运输 (generateDibangPlan=true)
|
||||
→ 每产品行:
|
||||
find_monthly_db_plan
|
||||
├─ 无 → create plan, planned_qty = contrib
|
||||
└─ 有 → planned_qty += contrib
|
||||
create inventory + link(contributed_qty=contrib)
|
||||
|
||||
调整运输
|
||||
→ revoke_regeneratable (每条 link: planned_qty -= contributed_qty)
|
||||
→ sync_dibang (同上累加)
|
||||
|
||||
取消计划
|
||||
→ revoke_regeneratable (同上回减)
|
||||
```
|
||||
|
||||
## 数据库变更
|
||||
|
||||
```sql
|
||||
ALTER TABLE pl_transport_db_link
|
||||
ADD COLUMN contributed_qty DECIMAL(15,3) NULL COMMENT '本次联动贡献量(吨)' AFTER db_inventory_id;
|
||||
```
|
||||
|
||||
历史数据:`contributed_qty` 为 NULL 时,回减 fallback 读取关联 `db_inbound_outbound.quantity`(作废前)。
|
||||
|
||||
## 影响面
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `docs/sql/pl_tables.sql` | 基线 DDL |
|
||||
| `docs/sql/20260711_pl_transport_db_link_contributed_qty.sql` | 增量迁移 |
|
||||
| `backend/app/models/pl_models.py` | ORM 字段 |
|
||||
| `backend/app/services/pl_dibang_bridge_service.py` | 累加/回减辅助函数 |
|
||||
| `backend/tests/test_pl_dibang_plan_quantity.py` | **新建** 单元测试 |
|
||||
|
||||
前端无需改动(行为对用户透明)。
|
||||
|
||||
## 风险与处理
|
||||
|
||||
| 风险 | 处理 |
|
||||
|------|------|
|
||||
| 历史 link 无 `contributed_qty` | 回减时 fallback `inventory.quantity` |
|
||||
| 扣减后 `planned_quantity < executed_quantity/1000` | 不回写 `executed_quantity`;执行状态由 `weighing_logic` 按现有规则重算 |
|
||||
| 手工创建的月计划基础量 | 运输联动仅在既有 `planned_quantity` 上累加/回减,不区分手工基数(本期接受) |
|
||||
@@ -0,0 +1,29 @@
|
||||
## Why
|
||||
|
||||
运输计划勾选「生成地磅单」联动地磅月计划时,当前实现仅在**首次创建**月计划时写入 `planned_quantity`,**复用**已有月计划时不更新计划量;取消计划或调整运输作废旧联动时也不回减。导致同月同往来单位、同产品多次运输后,月计划量与真实运输需求不一致。
|
||||
|
||||
## What Changes
|
||||
|
||||
- `pl_transport_db_link` 新增 `contributed_qty`(吨),记录单次运输联动对月计划的贡献量
|
||||
- 安排运输勾选地磅时:新建月计划 `planned_quantity = 贡献量`;复用月计划 `planned_quantity += 贡献量`
|
||||
- 调整运输、取消计划作废旧联动时:`planned_quantity -= contributed_qty`(下限 0)
|
||||
- 调整运输重建联动时:先回减旧贡献、再按新产品数量累加新贡献
|
||||
- 新增/补充单元测试覆盖累加、调整、取消三条路径
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Capability | Spec |
|
||||
|------------|------|
|
||||
| 地磅月计划量运输联动累加与回减 | `specs/dibang-plan-quantity/spec.md` |
|
||||
|
||||
## Non-goals
|
||||
|
||||
- 不修改地磅磅单(`db_weighing_slip`)生成逻辑
|
||||
- 不自动重算或回写 `executed_quantity`(仍由磅单业务逻辑维护)
|
||||
- 不删除 `planned_quantity = 0` 的月计划记录
|
||||
- 不改变「已关联磅单 / 已完成出入库单阻断作废」的现有门禁
|
||||
|
||||
## Dependencies
|
||||
|
||||
- 现有 `pl_dibang_bridge_service.py`、`POST /transport/order/arrange/{plan_id}`、`POST /transport/plan/{id}/cancel`
|
||||
- 前置设计:`docs/superpowers/specs/2026-06-20-pl-arrange-dibang-design.md`
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
# Dibang Plan Quantity (地磅月计划量运输联动)
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 运输联动地磅月计划量累加
|
||||
|
||||
当运输计划安排运输且勾选「生成地磅单」(`generateDibangPlan=true`)时,系统 SHALL 按产品行联动地磅月计划,并按运输计划产品数量更新月计划 `planned_quantity`(单位:吨)。
|
||||
|
||||
贡献量 SHALL 取自 `pl_plan_product`,优先级为:`quantity` → `inbound_estimate_qty` → `outbound_estimate_qty` → `0`。
|
||||
|
||||
月计划匹配维度 SHALL 与现有 `find_monthly_db_plan` 一致:同租户、同业务类型(入库/出库)、同 CRM `product_id`、同 `specification`、同 `customer_name`(往来单位),且计划有效期与运输计划地磅日期所在自然月相交。
|
||||
|
||||
#### Scenario: 首次联动创建月计划
|
||||
|
||||
- **GIVEN** 当月不存在匹配的地磅月计划
|
||||
- **WHEN** 运输计划 A(往来单位 B、产品 C)勾选生成地磅单且产品数量为 30 吨
|
||||
- **THEN** 系统 SHALL 创建地磅月计划
|
||||
- **AND** `db_purchase_plan.planned_quantity` SHALL 为 `30`
|
||||
- **AND** 系统 SHALL 创建出入库单与 `pl_transport_db_link`
|
||||
- **AND** `pl_transport_db_link.contributed_qty` SHALL 为 `30`
|
||||
|
||||
#### Scenario: 后续联动累加月计划
|
||||
|
||||
- **GIVEN** 当月已存在匹配的地磅月计划且 `planned_quantity` 为 `30`
|
||||
- **WHEN** 运输计划 B(同往来单位 B、同产品 C)勾选生成地磅单且产品数量为 31 吨
|
||||
- **THEN** 系统 SHALL 复用该地磅月计划(不新建第二条同维度月计划)
|
||||
- **AND** `db_purchase_plan.planned_quantity` SHALL 更新为 `61`
|
||||
- **AND** 系统 SHALL 新建出入库单与 `pl_transport_db_link`,`contributed_qty` 为 `31`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 调整运输时地磅月计划量回减与重建
|
||||
|
||||
当用户对已安排运输的计划执行「调整运输」且该运输单此前存在地磅联动时,系统 SHALL 在重建联动前作废仍可作废的出入库单与关联,并回减对应月计划贡献量;若本次仍勾选生成地磅单,系统 SHALL 按当前计划产品数量重新累加贡献量。
|
||||
|
||||
#### Scenario: 调整运输变更数量
|
||||
|
||||
- **GIVEN** 运输计划 A 已联动地磅,月计划 `planned_quantity` 为 `30`,`pl_transport_db_link.contributed_qty` 为 `30`
|
||||
- **WHEN** 用户对计划 A 执行调整运输,勾选生成地磅单,且产品数量变更为 `25` 吨
|
||||
- **THEN** 系统 SHALL 作废旧出入库单与关联,并将月计划 `planned_quantity` 回减为 `0`
|
||||
- **AND** 系统 SHALL 创建新出入库单与关联,`contributed_qty` 为 `25`
|
||||
- **AND** 月计划 `planned_quantity` SHALL 为 `25`
|
||||
|
||||
#### Scenario: 调整运输取消勾选地磅
|
||||
|
||||
- **GIVEN** 运输计划 A 已联动地磅,月计划 `planned_quantity` 为 `30`
|
||||
- **WHEN** 用户对计划 A 执行调整运输且**未**勾选生成地磅单
|
||||
- **THEN** 系统 SHALL 作废旧出入库单与关联,并将月计划 `planned_quantity` 回减为 `0`
|
||||
- **AND** 系统 SHALL NOT 创建新的地磅联动记录
|
||||
|
||||
#### Scenario: 调整运输被阻断
|
||||
|
||||
- **GIVEN** 运输计划 A 的联动出入库单已关联有效地磅磅单,或出入库单状态不在 `{待入库, 待出库, 待三方填写, 待处理}`
|
||||
- **WHEN** 用户对计划 A 执行调整运输
|
||||
- **THEN** 系统 SHALL 拒绝操作并提示需在地磅管理中先处理
|
||||
- **AND** 月计划 `planned_quantity` SHALL 保持不变
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 取消计划时地磅月计划量回减
|
||||
|
||||
当用户取消运输计划且该计划关联的运输单存在地磅联动时,系统 SHALL 作废仍可作废的出入库单与关联,并从对应地磅月计划回减贡献量。
|
||||
|
||||
#### Scenario: 取消计划回减月计划量
|
||||
|
||||
- **GIVEN** 运输计划 A(30 吨)与运输计划 B(31 吨)均已联动同一地磅月计划,`planned_quantity` 为 `61`
|
||||
- **WHEN** 用户取消运输计划 A
|
||||
- **THEN** 系统 SHALL 软删计划 A 的出入库单与 `pl_transport_db_link`
|
||||
- **AND** 地磅月计划 `planned_quantity` SHALL 更新为 `31`
|
||||
- **AND** 计划 A 的 `follow_status` SHALL 为 `cancelled`
|
||||
|
||||
#### Scenario: 取消计划被阻断
|
||||
|
||||
- **GIVEN** 运输计划 A 的联动出入库单已关联有效地磅磅单或状态不可作废
|
||||
- **WHEN** 用户取消运输计划 A
|
||||
- **THEN** 系统 SHALL 拒绝取消并提示需在地磅管理中先处理
|
||||
- **AND** 月计划 `planned_quantity` SHALL 保持不变
|
||||
|
||||
#### Scenario: 取消后月计划量下限
|
||||
|
||||
- **GIVEN** 仅运输计划 A(30 吨)联动地磅月计划,`planned_quantity` 为 `30`
|
||||
- **WHEN** 用户取消运输计划 A
|
||||
- **THEN** 月计划 `planned_quantity` SHALL 为 `0`
|
||||
- **AND** 系统 SHALL NOT 物理删除该 `db_purchase_plan` 记录
|
||||
@@ -0,0 +1,23 @@
|
||||
# Tasks: 地磅月计划量运输联动累加与回减
|
||||
|
||||
## 1. Schema 与迁移
|
||||
|
||||
- [x] 1.1 编写 `docs/sql/20260711_pl_transport_db_link_contributed_qty.sql`
|
||||
- [x] 1.2 同步 `docs/sql/pl_tables.sql` 中 `pl_transport_db_link.contributed_qty`
|
||||
- [x] 1.3 `backend/app/models/pl_models.py` 增加 `PlTransportDbLink.contributed_qty`
|
||||
|
||||
## 2. 桥接服务
|
||||
|
||||
- [x] 2.1 `pl_dibang_bridge_service.py` 新增 `_apply_plan_quantity_delta`、`_resolve_link_contributed_qty`
|
||||
- [x] 2.2 `_sync_transport_inventory_links`:复用月计划时 `+= contributed_qty`;写入 link.contributed_qty
|
||||
- [x] 2.3 `revoke_regeneratable_dibang_for_transport`:作废前 `-= contributed_qty`(下限 0)
|
||||
|
||||
## 3. 测试
|
||||
|
||||
- [x] 3.1 新建 `backend/tests/test_pl_dibang_plan_quantity.py`
|
||||
- [x] 3.2 覆盖:累加、回减、阻断(本地 venv 损坏,已用 python3 手工验证核心路径)
|
||||
|
||||
## 4. 验收
|
||||
|
||||
- [ ] 4.1 本地 pytest 通过(需修复 venv 或 `pip install pytest`)
|
||||
- [ ] 4.2 手工:A(30)+B(31)→61;取消 A→31;调整 A 30→25→净 25
|
||||
@@ -689,7 +689,6 @@
|
||||
"687": "Community 687",
|
||||
"688": "Community 688",
|
||||
"689": "Community 689",
|
||||
"690": "Community 690",
|
||||
"691": "Community 691",
|
||||
"692": "Community 692",
|
||||
"693": "Community 693",
|
||||
@@ -711,17 +710,21 @@
|
||||
"709": "Community 709",
|
||||
"710": "Community 710",
|
||||
"711": "Community 711",
|
||||
"712": "Community 712",
|
||||
"713": "Community 713",
|
||||
"714": "Community 714",
|
||||
"715": "Community 715",
|
||||
"716": "Community 716",
|
||||
"717": "Community 717",
|
||||
"718": "Community 718",
|
||||
"719": "Community 719",
|
||||
"721": "Community 721",
|
||||
"722": "Community 722",
|
||||
"723": "Community 723",
|
||||
"724": "Community 724",
|
||||
"725": "Community 725",
|
||||
"726": "Community 726",
|
||||
"727": "Community 727",
|
||||
"728": "Community 728",
|
||||
"729": "Community 729",
|
||||
"730": "Community 730",
|
||||
"731": "Community 731",
|
||||
"732": "Community 732",
|
||||
"733": "Community 733",
|
||||
@@ -729,5 +732,9 @@
|
||||
"735": "Community 735",
|
||||
"736": "Community 736",
|
||||
"737": "Community 737",
|
||||
"739": "Community 739"
|
||||
"738": "Community 738",
|
||||
"739": "Community 739",
|
||||
"740": "Community 740",
|
||||
"741": "Community 741",
|
||||
"743": "Community 743"
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
+4000
-3798
File diff suppressed because it is too large.
Load diff
@@ -2,7 +2,7 @@
|
||||
<el-dialog
|
||||
title="选择客户"
|
||||
v-model="dialogVisible"
|
||||
width="1280px"
|
||||
width="1500px"
|
||||
align-center
|
||||
class="dialog-form-layout"
|
||||
modal-class="order-module-dialog-mask"
|
||||
@@ -39,6 +39,7 @@
|
||||
<div class="panel dialog-list-panel">
|
||||
<div class="list-body list-wrap dialog-list-body-only">
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
:data="customerList"
|
||||
class="list-table"
|
||||
@@ -48,7 +49,13 @@
|
||||
:max-height="400"
|
||||
>
|
||||
<el-table-column type="index" label="序号" min-width="60" align="center" header-align="center" />
|
||||
<el-table-column label="客户名称" prop="customerName" min-width="230" align="center" header-align="center" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="客户名称" prop="customerName" min-width="200" align="center" header-align="center" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="客户简称" prop="shortName" min-width="120" align="center" header-align="center" :show-overflow-tooltip="true">
|
||||
<template #default="scope">
|
||||
<span v-if="hasShortName(scope.row)">{{ scope.row.shortName }}</span>
|
||||
<el-tag v-else type="warning" size="small">未填写</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="客户类型" prop="customerType" min-width="150" align="center" header-align="center">
|
||||
<template #default="scope">
|
||||
<DictTag dict-type="crm_customer_type" :value="scope.row.customerType" />
|
||||
@@ -72,6 +79,13 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="负责人" prop="ownerUserName" min-width="100" align="center" header-align="center" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="操作" fixed="right" min-width="120" align="center" header-align="center" class-name="col-actions">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click.stop="handleEditShortName(scope.row)">
|
||||
<el-icon><Edit /></el-icon>{{ hasShortName(scope.row) ? '编辑简称' : '填写简称' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<ListPagination
|
||||
@@ -90,6 +104,7 @@
|
||||
<div class="selected-customer">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="客户名称">{{ currentCustomer.customerName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户简称">{{ currentCustomer.shortName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户类型">
|
||||
<DictTag dict-type="crm_customer_type" :value="currentCustomer.customerType" />
|
||||
</el-descriptions-item>
|
||||
@@ -110,7 +125,55 @@
|
||||
<el-button class="btn-ghost" @click="handleClose">
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleConfirm" :disabled="!currentCustomer">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
@click="handleConfirm"
|
||||
:disabled="!currentCustomer || !hasShortName(currentCustomer)"
|
||||
>
|
||||
<el-icon><Check /></el-icon>确定
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 填写/编辑客户简称 -->
|
||||
<el-dialog
|
||||
v-model="shortNameDialogVisible"
|
||||
:title="hasShortName(shortNameForm) ? '编辑客户简称' : '填写客户简称'"
|
||||
width="480px"
|
||||
align-center
|
||||
class="dialog-form-layout"
|
||||
modal-class="order-module-dialog-mask"
|
||||
:close-on-click-modal="false"
|
||||
append-to-body
|
||||
>
|
||||
<el-form
|
||||
ref="shortNameFormRef"
|
||||
:model="shortNameForm"
|
||||
:rules="shortNameRules"
|
||||
label-position="top"
|
||||
class="crm-dialog-form"
|
||||
>
|
||||
<el-form-item label="客户名称">
|
||||
<el-input :model-value="shortNameForm.customerName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="客户简称" prop="shortName">
|
||||
<el-input
|
||||
v-model="shortNameForm.shortName"
|
||||
placeholder="请输入客户简称"
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button class="btn-ghost" @click="shortNameDialogVisible = false">
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="shortNameSaving" @click="handleSaveShortName">
|
||||
<el-icon><Check /></el-icon>确定
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -122,10 +185,10 @@
|
||||
import { useDictStore } from '@/store/modules/dict'
|
||||
import DictTag from '@/components/DictTag/index.vue'
|
||||
import ListPagination from '@/components/ListPagination/index.vue'
|
||||
import { Check, Close, Refresh, Search } from '@element-plus/icons-vue'
|
||||
import { Check, Close, Edit, Refresh, Search } from '@element-plus/icons-vue'
|
||||
import { ref, watch } from 'vue'
|
||||
import type { ElFormInstance } from 'element-plus'
|
||||
import { fetchCustomerList } from '@/api/crm/customer'
|
||||
import type { ElFormInstance, FormItemRule } from 'element-plus'
|
||||
import { fetchCustomerList, fetchUpdateCustomer } from '@/api/crm/customer'
|
||||
|
||||
const dictStore = useDictStore()
|
||||
|
||||
@@ -167,6 +230,21 @@ const loading = ref(false)
|
||||
const total = ref(0)
|
||||
const currentCustomer = ref<any | null>(null)
|
||||
const queryFormRef = ref<ElFormInstance>()
|
||||
const tableRef = ref<any>()
|
||||
|
||||
const shortNameDialogVisible = ref(false)
|
||||
const shortNameSaving = ref(false)
|
||||
const shortNameFormRef = ref<ElFormInstance>()
|
||||
const shortNameForm = ref({
|
||||
id: '' as string | number,
|
||||
customerName: '',
|
||||
shortName: ''
|
||||
})
|
||||
const shortNameRules: Record<string, FormItemRule[]> = {
|
||||
shortName: [{ required: true, message: '客户简称不能为空', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const hasShortName = (row?: { shortName?: string } | null) => Boolean(String(row?.shortName || '').trim())
|
||||
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
@@ -213,10 +291,66 @@ watchDebounced(
|
||||
)
|
||||
|
||||
const handleCurrentChange = (row: any) => {
|
||||
if (!row) {
|
||||
currentCustomer.value = null
|
||||
return
|
||||
}
|
||||
if (!hasShortName(row)) {
|
||||
ElMessage.warning('该客户尚未填写简称,请先填写客户简称后再选择')
|
||||
tableRef.value?.setCurrentRow(undefined)
|
||||
currentCustomer.value = null
|
||||
return
|
||||
}
|
||||
currentCustomer.value = row
|
||||
}
|
||||
|
||||
const handleEditShortName = (row: any) => {
|
||||
shortNameForm.value = {
|
||||
id: row.id,
|
||||
customerName: row.customerName || '',
|
||||
shortName: row.shortName || ''
|
||||
}
|
||||
shortNameDialogVisible.value = true
|
||||
nextTick(() => shortNameFormRef.value?.clearValidate())
|
||||
}
|
||||
|
||||
const handleSaveShortName = async () => {
|
||||
const valid = await shortNameFormRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
|
||||
shortNameSaving.value = true
|
||||
try {
|
||||
const trimmedShortName = shortNameForm.value.shortName.trim()
|
||||
await fetchUpdateCustomer({
|
||||
id: shortNameForm.value.id,
|
||||
shortName: trimmedShortName
|
||||
})
|
||||
ElMessage.success('客户简称保存成功')
|
||||
|
||||
const targetId = shortNameForm.value.id
|
||||
const updatedRow = customerList.value.find((item) => item.id === targetId)
|
||||
if (updatedRow) {
|
||||
updatedRow.shortName = trimmedShortName
|
||||
}
|
||||
if (currentCustomer.value?.id === targetId) {
|
||||
currentCustomer.value = { ...currentCustomer.value, shortName: trimmedShortName }
|
||||
}
|
||||
|
||||
shortNameDialogVisible.value = false
|
||||
} catch (error) {
|
||||
console.error('保存客户简称失败:', error)
|
||||
ElMessage.error('保存客户简称失败')
|
||||
} finally {
|
||||
shortNameSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (!currentCustomer.value) return
|
||||
if (!hasShortName(currentCustomer.value)) {
|
||||
ElMessage.warning('该客户尚未填写简称,请先填写客户简称后再选择')
|
||||
return
|
||||
}
|
||||
emit('confirm', currentCustomer.value)
|
||||
handleClose()
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-if="getCategoryLabel(file)" class="attachment-preview__category">
|
||||
{{ getCategoryLabel(file) }}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
v-if="compact && !disabled"
|
||||
@@ -165,6 +168,8 @@ const props = withDefaults(
|
||||
disabled?: boolean
|
||||
/** 紧凑模式:小缩略图,只读时隐藏文件名与操作栏 */
|
||||
compact?: boolean
|
||||
/** 附件 URL → 分类名称,用于查看态在缩略图上展示分类标签 */
|
||||
categoryLabels?: Record<string, string>
|
||||
}>(),
|
||||
{
|
||||
modelValue: '',
|
||||
@@ -173,7 +178,8 @@ const props = withDefaults(
|
||||
accept: 'image/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.zip,.rar',
|
||||
hint: '支持图片、视频、PDF、Word、Excel等文件',
|
||||
disabled: false,
|
||||
compact: false
|
||||
compact: false,
|
||||
categoryLabels: () => ({})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -269,6 +275,11 @@ function getFileExtension(fileName: string) {
|
||||
return fileName.substring(lastDotIndex + 1).toUpperCase()
|
||||
}
|
||||
|
||||
function getCategoryLabel(file: AttachmentItem) {
|
||||
const label = props.categoryLabels?.[file.fileUrl]
|
||||
return label ? String(label).trim() : ''
|
||||
}
|
||||
|
||||
function canPreview(file: AttachmentItem) {
|
||||
return Boolean(file.fileUrl) && (isImageFile(file) || isVideoFile(file) || isPdfFile(file))
|
||||
}
|
||||
@@ -420,6 +431,7 @@ defineExpose({ hasUploading })
|
||||
}
|
||||
|
||||
.attachment-item {
|
||||
flex: 0 0 auto;
|
||||
width: 140px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
@@ -438,6 +450,7 @@ defineExpose({ hasUploading })
|
||||
}
|
||||
|
||||
.attachment-preview {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 96px;
|
||||
display: flex;
|
||||
@@ -490,6 +503,21 @@ defineExpose({ hasUploading })
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.attachment-preview__category {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
padding: 2px 4px;
|
||||
background: rgba(15, 23, 42, 0.62);
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
.attachment-footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -565,7 +593,8 @@ defineExpose({ hasUploading })
|
||||
}
|
||||
|
||||
:deep(.el-upload) {
|
||||
display: block;
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.attachment-upload--compact {
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
调整运输时现场链接将轮换更新,旧现场链接将失效。
|
||||
</template>
|
||||
<template v-else-if="showDibangHint">
|
||||
勾选后将按产品进行地磅单生成(当月已有则复用),并在地磅出入库管理中产生对应记录。
|
||||
勾选后将按产品进行地磅单生成(当月已有则复用);入库单日期取送达时间,出库单日期取取货时间,未填时回退计划日期。
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div class="view-kv-table-cell">
|
||||
<el-tag v-if="field.tagType" :type="field.tagType" size="small">{{
|
||||
<el-tag v-if="field.tagType" :type="field.tagType" size="small" class="view-kv-tag">{{
|
||||
formatViewValue(field.value)
|
||||
}}</el-tag>
|
||||
<ul v-else-if="field.items?.length" class="view-plain-list">
|
||||
<li v-for="(item, idx) in field.items" :key="idx">{{ item }}</li>
|
||||
</ul>
|
||||
<span v-else :class="{ 'money-display': field.money, 'muted-display': isMuted }">{{
|
||||
<span v-else :class="{ 'money-display': field.money, 'danger-display': field.danger, 'muted-display': isMuted }">{{
|
||||
formatViewValue(field.value)
|
||||
}}</span>
|
||||
<el-button
|
||||
@@ -49,7 +49,7 @@
|
||||
const onFieldView = inject<((field: ViewField) => void) | undefined>(VIEW_KV_FIELD_VIEW_KEY)
|
||||
|
||||
const isMuted = computed(
|
||||
() => !props.field.money && formatViewValue(props.field.value) === '—'
|
||||
() => !props.field.money && !props.field.danger && formatViewValue(props.field.value) === '—'
|
||||
)
|
||||
|
||||
const showViewBtn = computed(() => {
|
||||
@@ -91,6 +91,10 @@
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.view-kv-tag {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.view-kv-field-link {
|
||||
margin: 0;
|
||||
padding: 0 4px;
|
||||
@@ -112,4 +116,9 @@
|
||||
color: var(--text-muted, #64748b);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.danger-display {
|
||||
color: #dc2626;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -18,6 +18,8 @@ export interface ViewField {
|
||||
fullSpan?: boolean
|
||||
tagType?: TagType
|
||||
money?: boolean
|
||||
/** 红色加粗强调展示(如结算方式等关键业务字段) */
|
||||
danger?: boolean
|
||||
/** 值后展示「查看」按钮,点击时 ViewKvTable 抛出 field-view */
|
||||
viewTarget?: 'customer' | 'opportunity' | 'quote'
|
||||
viewId?: string | number | null
|
||||
|
||||
@@ -141,14 +141,16 @@ export function isHazardCertReady(plan: { hazardCertReady?: number } | null | un
|
||||
return Number(plan?.hazardCertReady ?? 0) === 1
|
||||
}
|
||||
|
||||
/** 含危化品且尚未人工确认证书齐备时,阻断安排运输 */
|
||||
/** 含危化品且尚未确认证书齐备(人工确认或全部所需证书已上传)时,阻断安排运输 */
|
||||
export function shouldBlockTransportForHazardCert(
|
||||
plan: { hazardCertReady?: number; products?: PlanProduct[] } | null | undefined,
|
||||
products?: PlanProduct[]
|
||||
) {
|
||||
const prods = products ?? plan?.products
|
||||
if (!planHasHazardProducts(prods)) return false
|
||||
return !isHazardCertReady(plan)
|
||||
if (isHazardCertReady(plan)) return false
|
||||
if (!findMissingHazardCerts(prods ?? []).length) return false
|
||||
return true
|
||||
}
|
||||
|
||||
export function buildMissingHazardCertReadyMessage(
|
||||
@@ -156,7 +158,10 @@ export function buildMissingHazardCertReadyMessage(
|
||||
products?: PlanProduct[]
|
||||
) {
|
||||
if (!shouldBlockTransportForHazardCert(plan, products)) return ''
|
||||
return '该计划危化品证书尚未确认齐备'
|
||||
const prods = products ?? plan?.products ?? []
|
||||
const missingCertsMsg = buildMissingHazardCertMessage(prods)
|
||||
if (missingCertsMsg) return missingCertsMsg
|
||||
return '该计划危化品证书尚未确认齐备,请点击「证书齐备」或补全所需证书'
|
||||
}
|
||||
|
||||
function formatMissingCertLabels(product: PlanProduct): string {
|
||||
|
||||
@@ -280,9 +280,29 @@ async function handleDynamicRoutes(
|
||||
// 2. 获取菜单数据
|
||||
const menuList = await menuProcessor.getMenuList()
|
||||
|
||||
// 3. 验证菜单数据
|
||||
// 3. 无菜单权限(如未绑定角色):允许空菜单并完成初始化,跳转 403
|
||||
if (!menuProcessor.validateMenuList(menuList)) {
|
||||
throw new Error('获取菜单列表失败,请重新登录')
|
||||
routeRegistry?.register([])
|
||||
const menuStore = useMenuStore()
|
||||
menuStore.setMenuList([])
|
||||
menuStore.addRemoveRouteFns(routeRegistry?.getRemoveRouteFns() || [])
|
||||
IframeRouteManager.getInstance().save()
|
||||
useWorktabStore().validateWorktabs(router)
|
||||
routeInitInProgress = false
|
||||
closeLoading()
|
||||
|
||||
if (isStaticRoute(to.path)) {
|
||||
next({
|
||||
path: to.path,
|
||||
query: to.query,
|
||||
hash: to.hash,
|
||||
replace: true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
next({ path: '/403', replace: true })
|
||||
return
|
||||
}
|
||||
|
||||
// 4. 注册动态路由
|
||||
|
||||
@@ -193,8 +193,9 @@
|
||||
if (c.mobile) parts.push(c.mobile)
|
||||
if (c.status) {
|
||||
parts.push(dictStore.getLabel('crm_clue_status', c.status))
|
||||
} else if (c.isConverted === 1) {
|
||||
parts.push('已转化')
|
||||
}
|
||||
if (c.isConverted === 1) parts.push('已转化')
|
||||
return parts.join(' · ')
|
||||
})
|
||||
|
||||
@@ -338,7 +339,6 @@
|
||||
}
|
||||
|
||||
const handleInvalidSuccess = async () => {
|
||||
ElMessage.success('标记无效成功')
|
||||
await loadDetail()
|
||||
emit('success')
|
||||
}
|
||||
@@ -350,7 +350,6 @@
|
||||
}
|
||||
|
||||
const handleConvertSuccess = async () => {
|
||||
ElMessage.success('转化成功')
|
||||
await loadDetail()
|
||||
emit('success')
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
v-model="attachmentUrls"
|
||||
folder="crm/clue/follow"
|
||||
:limit="15"
|
||||
hint="支持图片、视频、PDF、Word 等文件,单文件不超过 20MB,最多 15 个"
|
||||
hint="支持图片、视频、PDF、Word 等文件,最多 15 个"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-position="right"
|
||||
label-width="100px"
|
||||
label-position="top"
|
||||
class="crm-dialog-form"
|
||||
scroll-to-error
|
||||
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
|
||||
|
||||
@@ -1381,7 +1381,11 @@ const detailBasicViewFields = computed<ViewField[]>(() => {
|
||||
? []
|
||||
: [{ label: '合同金额(元)', value: `¥${formatDetailAmount(c.amount)}`, money: true }]),
|
||||
{ label: '交易主体', value: c.supplierName },
|
||||
detailTagField('结算方式', 'crm_settlement_method', c.settlementMethod)
|
||||
{
|
||||
label: '结算方式',
|
||||
value: c.settlementMethod ? dictStore.getLabel('crm_settlement_method', c.settlementMethod) : '—',
|
||||
danger: true
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
|
||||
@@ -279,7 +279,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="合同编号" align="center" prop="contractNo" min-width="170" show-overflow-tooltip />
|
||||
<el-table-column label="合同名称" prop="contractName" show-overflow-tooltip min-width="200">
|
||||
<el-table-column label="合同名称" prop="contractName" show-overflow-tooltip min-width="280">
|
||||
<template #default="scope">
|
||||
<el-link type="primary" @click="handleView(scope.row)">{{ scope.row.contractName }}</el-link>
|
||||
</template>
|
||||
@@ -349,14 +349,6 @@
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isColumnVisible('receivedAmount')" label="已回款" align="right" prop="receivedAmount" min-width="130">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.receivedAmount && scope.row.receivedAmount > 0" class="text-success"
|
||||
>¥{{ parseFloat(scope.row.receivedAmount).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}</span
|
||||
>
|
||||
<span v-else class="text-muted">¥0.00</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isColumnVisible('unreceivedAmount')" label="未回款" align="right" prop="unreceivedAmount" min-width="130">
|
||||
<template #default="scope">
|
||||
<span
|
||||
@@ -366,14 +358,6 @@
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isColumnVisible('receivedRate')" label="回款进度" align="center" min-width="120">
|
||||
<template #default="scope">
|
||||
<el-progress
|
||||
:percentage="getReceivedRate(scope.row.contractAmount, scope.row.receivedAmount)"
|
||||
:color="getProgressColor(getReceivedRate(scope.row.contractAmount, scope.row.receivedAmount))"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isColumnVisible('invoiceAmount')" label="已开票金额" align="right" prop="invoiceAmount" min-width="130">
|
||||
<template #default="scope">
|
||||
<span
|
||||
@@ -421,6 +405,22 @@
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isColumnVisible('receivedAmount')" label="已回款" align="right" prop="receivedAmount" min-width="130">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.receivedAmount && scope.row.receivedAmount > 0" class="text-success"
|
||||
>¥{{ parseFloat(scope.row.receivedAmount).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}</span
|
||||
>
|
||||
<span v-else class="text-muted">¥0.00</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isColumnVisible('receivedRate')" label="回款进度" align="center" min-width="120">
|
||||
<template #default="scope">
|
||||
<el-progress
|
||||
:percentage="getReceivedRate(scope.row.contractAmount, scope.row.receivedAmount)"
|
||||
:color="getProgressColor(getReceivedRate(scope.row.contractAmount, scope.row.receivedAmount))"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isColumnVisible('ownerUserName')"
|
||||
label="负责人"
|
||||
@@ -438,7 +438,7 @@
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column v-if="isColumnVisible('createByName')" label="创建人" align="center" prop="createByName" min-width="100" />
|
||||
<el-table-column v-if="isColumnVisible('createTime')" label="创建时间" align="center" prop="createTime" min-width="160" show-overflow-tooltip>
|
||||
<el-table-column v-if="isColumnVisible('createTime')" label="创建时间" align="center" prop="createTime" min-width="140" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
@@ -581,9 +581,7 @@ const listColumnDefs = [
|
||||
{ prop: 'renewalCount', label: '续签次数' },
|
||||
{ prop: 'attachmentFiles', label: '合同附件' },
|
||||
{ prop: 'amount', label: '合同金额(元)' },
|
||||
{ prop: 'receivedAmount', label: '已回款' },
|
||||
{ prop: 'unreceivedAmount', label: '未回款' },
|
||||
{ prop: 'receivedRate', label: '回款进度' },
|
||||
{ prop: 'invoiceAmount', label: '已开票金额' },
|
||||
{ prop: 'uninvoiceAmount', label: '未开票金额' },
|
||||
{ prop: 'status', label: '合同状态' },
|
||||
@@ -595,6 +593,8 @@ const listColumnDefs = [
|
||||
{ prop: 'partyA', label: '甲方' },
|
||||
{ prop: 'partyB', label: '乙方' },
|
||||
{ prop: 'settlementMethod', label: '结算方式' },
|
||||
{ prop: 'receivedAmount', label: '已回款' },
|
||||
{ prop: 'receivedRate', label: '回款进度' },
|
||||
{ prop: 'ownerUserName', label: '负责人' },
|
||||
{ prop: 'createDeptName', label: '创建部门' },
|
||||
{ prop: 'createByName', label: '创建人' },
|
||||
@@ -1342,4 +1342,106 @@ onMounted(async () => {
|
||||
color: var(--mint-primary, #1ca851);
|
||||
}
|
||||
|
||||
.attachment-list-cell {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 8px;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
white-space: nowrap;
|
||||
padding: 4px 0;
|
||||
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.attachment-thumb-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
width: 58px;
|
||||
}
|
||||
|
||||
.attachment-preview-wrap {
|
||||
position: relative;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.attachment-thumb-image {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e2e8f0;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.attachment-file-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: #f8fafc;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
.el-icon {
|
||||
font-size: 22px;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: #1ca851;
|
||||
background: #e6f8ed;
|
||||
|
||||
.el-icon {
|
||||
color: #1ca851;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-download-btn {
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
bottom: -4px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
color: #1e293b;
|
||||
cursor: pointer;
|
||||
z-index: 2;
|
||||
|
||||
&:hover {
|
||||
color: #1ca851;
|
||||
border-color: #1ca851;
|
||||
background: #e6f8ed;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-category-label {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-size: 11px;
|
||||
line-height: 1.3;
|
||||
color: #1e293b;
|
||||
text-align: center;
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -15,20 +15,11 @@
|
||||
append-to-body
|
||||
>
|
||||
<template #certificates>
|
||||
<div class="cert-list">
|
||||
<div v-if="customerInfo.businessLicenseImages" class="cert-block">
|
||||
<div class="view-field-label">营业执照</div>
|
||||
<AttachmentUploadField :model-value="customerInfo.businessLicenseImages" disabled />
|
||||
</div>
|
||||
<div v-if="customerInfo.businessPermitImages" class="cert-block">
|
||||
<div class="view-field-label">经营许可证</div>
|
||||
<AttachmentUploadField :model-value="customerInfo.businessPermitImages" disabled />
|
||||
</div>
|
||||
<div v-if="customerInfo.otherCertImages" class="cert-block">
|
||||
<div class="view-field-label">其他证件</div>
|
||||
<AttachmentUploadField :model-value="customerInfo.otherCertImages" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<AttachmentUploadField
|
||||
:model-value="certificateDisplay.modelValue"
|
||||
:category-labels="certificateDisplay.categoryLabels"
|
||||
disabled
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #hero-actions>
|
||||
@@ -547,6 +538,32 @@ const isToday = computed(() => {
|
||||
return today === customerInfo.value.nextTime.split(' ')[0]
|
||||
})
|
||||
|
||||
function appendCertUrls(raw: string | undefined, label: string, urls: string[], labels: Record<string, string>) {
|
||||
if (!raw) return
|
||||
String(raw)
|
||||
.split(',')
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
.forEach((url) => {
|
||||
urls.push(url)
|
||||
labels[url] = label
|
||||
})
|
||||
}
|
||||
|
||||
function buildCertDisplay(customer: CustomerVO) {
|
||||
const urls: string[] = []
|
||||
const labels: Record<string, string> = {}
|
||||
appendCertUrls(customer.businessLicenseImages, '营业执照', urls, labels)
|
||||
appendCertUrls(customer.businessPermitImages, '经营许可证', urls, labels)
|
||||
appendCertUrls(customer.otherCertImages, '其他证件', urls, labels)
|
||||
return {
|
||||
modelValue: urls.join(','),
|
||||
categoryLabels: labels
|
||||
}
|
||||
}
|
||||
|
||||
const certificateDisplay = computed(() => buildCertDisplay(customerInfo.value))
|
||||
|
||||
const viewHeroMeta = computed(() => {
|
||||
const c = customerInfo.value
|
||||
const parts: string[] = []
|
||||
@@ -993,18 +1010,6 @@ defineExpose({ open })
|
||||
}
|
||||
}
|
||||
|
||||
.cert-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.cert-block {
|
||||
.view-field-label {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-timeline-item__timestamp) {
|
||||
color: #1e293b;
|
||||
font-size: 12px;
|
||||
|
||||
@@ -152,25 +152,25 @@
|
||||
|
||||
<el-divider content-position="left">证件信息</el-divider>
|
||||
<div class="form-grid-4">
|
||||
<el-form-item label="营业执照" prop="businessLicenseImages">
|
||||
<el-form-item label="营业执照" prop="businessLicenseImages" class="form-field-span-2">
|
||||
<AttachmentUploadField
|
||||
ref="businessLicenseUploadRef"
|
||||
v-model="form.businessLicenseImages"
|
||||
:limit="5"
|
||||
accept="image/*,.pdf"
|
||||
folder="crm/customer/license"
|
||||
hint="支持 JPG、PNG、PDF,单文件不超过 20MB,最多 5 个"
|
||||
hint="支持 JPG、PNG、PDF,最多 5 个"
|
||||
@update:model-value="handleCertFieldChange('businessLicenseImages')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="经营许可证" prop="businessPermitImages">
|
||||
<el-form-item label="经营许可证" prop="businessPermitImages" class="form-field-span-2">
|
||||
<AttachmentUploadField
|
||||
ref="businessPermitUploadRef"
|
||||
v-model="form.businessPermitImages"
|
||||
:limit="5"
|
||||
accept="image/*,.pdf"
|
||||
folder="crm/customer/permit"
|
||||
hint="支持 JPG、PNG、PDF,单文件不超过 20MB,最多 5 个"
|
||||
hint="支持 JPG、PNG、PDF,最多 5 个"
|
||||
@update:model-value="handleCertFieldChange('businessPermitImages')"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -181,7 +181,7 @@
|
||||
:limit="10"
|
||||
accept="image/*,.pdf"
|
||||
folder="crm/customer/cert"
|
||||
hint="支持 JPG、PNG、PDF,单文件不超过 20MB,最多 10 个"
|
||||
hint="支持 JPG、PNG、PDF,最多 10 个"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
@@ -519,6 +519,7 @@ const open = async (id?: number | string) => {
|
||||
Object.assign(form.value, data)
|
||||
form.value.businessLicenseImages =
|
||||
(data as any)?.businessLicenseImages || (data as any)?.businessLicenseImg || undefined
|
||||
delete (form.value as Record<string, unknown>).businessLicenseImg
|
||||
form.value.businessPermitImages = (data as any)?.businessPermitImages || undefined
|
||||
form.value.otherCertImages = (data as any)?.otherCertImages || undefined
|
||||
if ((data as any)?.ownerUserId) {
|
||||
@@ -536,6 +537,8 @@ const open = async (id?: number | string) => {
|
||||
/** 构建提交 payload(字段名与后端 Schema 对齐) */
|
||||
function buildSubmitPayload() {
|
||||
const payload: Record<string, any> = { ...form.value }
|
||||
// 详情接口会同时返回 businessLicenseImg(旧字段),避免覆盖新上传的 businessLicenseImages
|
||||
delete payload.businessLicenseImg
|
||||
return payload
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
v-model="attachmentUrls"
|
||||
folder="crm/customer/follow"
|
||||
:limit="15"
|
||||
hint="支持图片、视频、PDF、Word、Excel等文件,单文件不超过 20MB,最多 15 个"
|
||||
hint="支持图片、视频、PDF、Word、Excel等文件,最多 15 个"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
v-model="attachmentUrls"
|
||||
folder="crm/opportunity/follow"
|
||||
:limit="15"
|
||||
hint="支持图片、视频、PDF、Word 等文件,单文件不超过 20MB,最多 15 个"
|
||||
hint="支持图片、视频、PDF、Word 等文件,最多 15 个"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
@@ -201,18 +201,6 @@
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="isColumnVisible('unit')"
|
||||
prop="unit"
|
||||
label="单位"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ formatProductUnit(row.unit) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isColumnVisible('price')"
|
||||
prop="price"
|
||||
@@ -293,7 +281,7 @@
|
||||
>
|
||||
<div class="form-grid-3">
|
||||
<el-form-item label="产品编号">
|
||||
<el-input v-model="formData.productNo" placeholder="留空自动生成" />
|
||||
<el-input v-model="formData.productNo" placeholder="留空自动生成(如 SXWL000001)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产品名称" prop="productName">
|
||||
<el-input v-model="formData.productName" placeholder="请输入产品名称" />
|
||||
@@ -367,7 +355,7 @@
|
||||
:limit="5"
|
||||
accept="image/*"
|
||||
folder="crm/product"
|
||||
hint="支持 JPG、PNG、WebP 等图片,单文件不超过 20MB,最多 5 张"
|
||||
hint="支持 JPG、PNG、WebP 等图片,最多 5 张"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="产品描述" style="grid-column: 1 / -1">
|
||||
@@ -548,11 +536,6 @@ function getHazardFlagTagType(value: unknown) {
|
||||
return 'danger'
|
||||
}
|
||||
|
||||
function formatProductUnit(unit: string | null | undefined) {
|
||||
if (!unit) return '-'
|
||||
return dictStore.getLabel('crm_product_unit', unit) || unit
|
||||
}
|
||||
|
||||
function formatProductPrice(value: unknown) {
|
||||
if (value === null || value === undefined || value === '') return '-'
|
||||
const num = Number(value)
|
||||
@@ -568,7 +551,6 @@ const listColumnDefs = [
|
||||
{ prop: 'productNo', label: '产品编号' },
|
||||
{ prop: 'categoryName', label: '产品分类' },
|
||||
{ prop: 'model', label: '规格型号' },
|
||||
{ prop: 'unit', label: '单位' },
|
||||
{ prop: 'price', label: '标准价(元)' },
|
||||
{ prop: 'cost', label: '成本价(元)', defaultVisible: false },
|
||||
{ prop: 'status', label: '状态' }
|
||||
@@ -648,22 +630,10 @@ const productViewSections = computed<ViewSection[]>(() => {
|
||||
if (!d) return []
|
||||
|
||||
const fields: ViewField[] = [
|
||||
{
|
||||
label: '标准价(元)',
|
||||
value: formatProductPrice(d.price),
|
||||
money: true
|
||||
},
|
||||
{
|
||||
label: '成本价(元)',
|
||||
value: formatProductPrice(d.costPrice ?? d.cost),
|
||||
money: true
|
||||
},
|
||||
crmField('库存数量', `${d.stock ?? 0} ${formatProductUnit(d.unit)}`),
|
||||
crmDictField('状态', 'crm_product_status', d.status, dictStore),
|
||||
crmField('产品分类', d.categoryName),
|
||||
crmField('规格型号', d.model),
|
||||
crmField('条码', d.barcode),
|
||||
crmField('库存预警值', d.lowStock ?? 10),
|
||||
crmField('销售次数', d.saleCount ?? 0),
|
||||
crmField('税率', `${d.taxRate ?? 13}%`),
|
||||
crmTagField('是否易制毒', formatHazardFlag(d.isToxic), getHazardFlagTagType(d.isToxic)),
|
||||
|
||||
@@ -203,47 +203,50 @@
|
||||
|
||||
<div class="quote-approval-flow-scroll">
|
||||
<p v-if="approvalList.length === 0" class="view-empty-hint">暂无审批记录</p>
|
||||
<el-timeline v-else>
|
||||
<el-timeline-item
|
||||
v-for="item in approvalList"
|
||||
:key="item.id"
|
||||
:timestamp="`第${item.step}步 · ${item.createTime || '—'}`"
|
||||
placement="top"
|
||||
:color="getApprovalTimelineColor(item.approvalResult)"
|
||||
:hollow="item.approvalResult === 'pending'"
|
||||
>
|
||||
<div class="quote-approval-card">
|
||||
<div class="quote-approval-card-head">
|
||||
<div class="quote-approval-card-user">
|
||||
<el-avatar :size="28">{{ item.approverName?.substring(0, 1) || '—' }}</el-avatar>
|
||||
<span>{{ item.approverName || '—' }}</span>
|
||||
<div v-else class="quote-approval-flow-track">
|
||||
<template v-for="(item, index) in approvalList" :key="item.id">
|
||||
<div class="quote-approval-flow-step">
|
||||
<p class="quote-approval-step-meta">
|
||||
第{{ item.step }}步 · {{ item.createTime || '—' }}
|
||||
</p>
|
||||
<div class="quote-approval-card">
|
||||
<div class="quote-approval-card-head">
|
||||
<div class="quote-approval-card-user">
|
||||
<el-avatar :size="28">{{ item.approverName?.substring(0, 1) || '—' }}</el-avatar>
|
||||
<span>{{ item.approverName || '—' }}</span>
|
||||
</div>
|
||||
<el-tag
|
||||
v-if="item.approvalResult === 'approved'"
|
||||
type="success"
|
||||
size="small"
|
||||
>
|
||||
已通过
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-else-if="item.approvalResult === 'rejected'"
|
||||
type="danger"
|
||||
size="small"
|
||||
>
|
||||
已拒绝
|
||||
</el-tag>
|
||||
<el-tag v-else type="info" size="small">待审批</el-tag>
|
||||
</div>
|
||||
<el-tag
|
||||
v-if="item.approvalResult === 'approved'"
|
||||
type="success"
|
||||
size="small"
|
||||
>
|
||||
已通过
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-else-if="item.approvalResult === 'rejected'"
|
||||
type="danger"
|
||||
size="small"
|
||||
>
|
||||
已拒绝
|
||||
</el-tag>
|
||||
<el-tag v-else type="info" size="small">待审批</el-tag>
|
||||
<p v-if="item.approvalOpinion" class="quote-approval-opinion">
|
||||
审批意见:{{ item.approvalOpinion }}
|
||||
</p>
|
||||
<p v-if="item.approvalTime" class="quote-approval-meta">
|
||||
审批时间:{{ item.approvalTime }}
|
||||
</p>
|
||||
<p v-if="item.remark" class="quote-approval-meta">备注:{{ item.remark }}</p>
|
||||
</div>
|
||||
<p v-if="item.approvalOpinion" class="quote-approval-opinion">
|
||||
审批意见:{{ item.approvalOpinion }}
|
||||
</p>
|
||||
<p v-if="item.approvalTime" class="quote-approval-meta">
|
||||
审批时间:{{ item.approvalTime }}
|
||||
</p>
|
||||
<p v-if="item.remark" class="quote-approval-meta">备注:{{ item.remark }}</p>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
<div
|
||||
v-if="index < approvalList.length - 1"
|
||||
class="quote-approval-flow-connector"
|
||||
:class="{ 'quote-approval-flow-connector--done': item.approvalResult === 'approved' }"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="approvalList.length > 0" class="quote-approval-stats">
|
||||
@@ -386,12 +389,6 @@ function getDaysRemaining(expireDate?: string) {
|
||||
return `剩余${diff}天`
|
||||
}
|
||||
|
||||
function getApprovalTimelineColor(result?: string) {
|
||||
if (result === 'approved') return '#1ca851'
|
||||
if (result === 'rejected') return '#ef4444'
|
||||
return '#94a3b8'
|
||||
}
|
||||
|
||||
/** 8 列表格区块:禁用长字段独占行,保证每行尽量排满 4 组 */
|
||||
function viewQuadTableSection(title: string, fields: ViewField[]): ViewSection {
|
||||
return {
|
||||
@@ -622,12 +619,62 @@ defineExpose({ open, refresh })
|
||||
}
|
||||
|
||||
.quote-approval-flow-scroll {
|
||||
max-height: 420px;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding: 4px 4px 12px;
|
||||
}
|
||||
|
||||
.quote-approval-flow-track {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 0;
|
||||
min-width: min-content;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.quote-approval-flow-step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 240px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.quote-approval-step-meta {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
}
|
||||
|
||||
.quote-approval-flow-connector {
|
||||
width: 48px;
|
||||
height: 2px;
|
||||
margin-top: 52px;
|
||||
flex-shrink: 0;
|
||||
align-self: flex-start;
|
||||
background: #e2e8f0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.quote-approval-flow-connector--done {
|
||||
background: linear-gradient(90deg, #1ca851, #49be78);
|
||||
}
|
||||
|
||||
.quote-approval-flow-connector--done::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -3px;
|
||||
top: -3px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-top: 2px solid #1ca851;
|
||||
border-right: 2px solid #1ca851;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.quote-approval-card {
|
||||
flex: 1;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--border, #d9e8e2);
|
||||
border-radius: 8px;
|
||||
|
||||
@@ -5,14 +5,17 @@
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-position="right"
|
||||
label-width="100px"
|
||||
label-width="120px"
|
||||
class="crm-dialog-form quote-form-body"
|
||||
scroll-to-error
|
||||
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
|
||||
>
|
||||
<el-divider content-position="left">基本信息</el-divider>
|
||||
<el-divider content-position="left">
|
||||
<span class="quote-section-title">基本信息</span>
|
||||
</el-divider>
|
||||
<div class="form-grid-4">
|
||||
<el-form-item class="quote-type-item form-col-span-4" label="报价类型" prop="quoteType" required>
|
||||
<!-- 第一行:报价类型、报价单编号、报价主题、关联商机 -->
|
||||
<el-form-item class="quote-type-item" label="报价类型" prop="quoteType" required>
|
||||
<el-radio-group v-model="form.quoteType" @change="handleQuoteTypeChange">
|
||||
<el-radio-button v-for="item in quoteTypeOptions" :key="item.value" :label="item.value">
|
||||
{{ item.label }}
|
||||
@@ -25,6 +28,27 @@
|
||||
<el-form-item :label="basicLabels.quoteTitle" prop="quoteTitle">
|
||||
<el-input v-model="form.quoteTitle" placeholder="可不填,系统将自动生成" maxlength="100" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item :label="basicLabels.opportunity" prop="opportunityId">
|
||||
<el-input v-model="opportunityDisplayName" placeholder="请选择商机(可选)" readonly clearable>
|
||||
<template #append>
|
||||
<el-button text @click="handleSelectOpportunity">
|
||||
<el-icon><Search /></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<!-- 第二行:采购主体/报价主体、客户/供货商名称、联系人、负责人 -->
|
||||
<el-form-item :label="basicLabels.supplier" prop="supplierId" required>
|
||||
<el-select
|
||||
v-model="form.supplierId"
|
||||
:placeholder="`请选择${basicLabels.supplier}`"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@change="handleSupplierChange"
|
||||
>
|
||||
<el-option v-for="supplier in supplierList" :key="supplier.id" :label="supplier.supplierName" :value="supplier.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="basicLabels.customerName" prop="customerId" required>
|
||||
<el-input v-model="form.customerName" placeholder="请选择客户" readonly>
|
||||
<template #append>
|
||||
@@ -43,15 +67,6 @@
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="basicLabels.opportunity" prop="opportunityId">
|
||||
<el-input v-model="opportunityDisplayName" placeholder="请选择商机(可选)" readonly clearable>
|
||||
<template #append>
|
||||
<el-button text @click="handleSelectOpportunity">
|
||||
<el-icon><Search /></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="basicLabels.ownerUser" prop="ownerUserId" required>
|
||||
<el-select
|
||||
v-model="form.ownerUserId"
|
||||
@@ -64,6 +79,10 @@
|
||||
<el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- 第三行:运费、询价/报价日期、有效期(天) -->
|
||||
<el-form-item label="运费(元)" prop="freight">
|
||||
<el-input-number v-model="form.freight" :min="0" :step="0.01" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="basicLabels.quoteDate" prop="quoteDate" required>
|
||||
<el-date-picker
|
||||
v-model="form.quoteDate"
|
||||
@@ -76,25 +95,11 @@
|
||||
<el-form-item :label="basicLabels.validityDays" prop="validityDays" required>
|
||||
<el-input-number v-model="form.validityDays" :min="1" :max="365" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="basicLabels.supplier" prop="supplierId" required>
|
||||
<el-select
|
||||
v-model="form.supplierId"
|
||||
:placeholder="`请选择${basicLabels.supplier}`"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@change="handleSupplierChange"
|
||||
>
|
||||
<el-option v-for="supplier in supplierList" :key="supplier.id" :label="supplier.supplierName" :value="supplier.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="运费(元)" prop="freight">
|
||||
<el-input-number v-model="form.freight" :min="0" :step="0.01" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 产品明细 -->
|
||||
<el-divider content-position="left">
|
||||
<span style="font-size: 16px; font-weight: bold; color: #67c23a">
|
||||
<span class="quote-section-title">
|
||||
<el-icon><GoodsFilled /></el-icon> 产品明细
|
||||
</span>
|
||||
</el-divider>
|
||||
@@ -236,7 +241,7 @@
|
||||
|
||||
<!-- 报价条款 -->
|
||||
<el-divider content-position="left">
|
||||
<span style="font-size: 16px; font-weight: bold; color: #67c23a">
|
||||
<span class="quote-section-title">
|
||||
<el-icon><List /></el-icon> 报价条款
|
||||
</span>
|
||||
</el-divider>
|
||||
@@ -891,8 +896,8 @@ const handleCustomerConfirm = async (customer: CustomerVO) => {
|
||||
if (customer) {
|
||||
form.value.customerId = customer.id;
|
||||
form.value.customerName = customer.customerName;
|
||||
// 保存客户简称
|
||||
customerShortName.value = customer.shortName || customer.customerName || '';
|
||||
// 保存客户简称(报价主题依赖简称,不可回退为客户全称)
|
||||
customerShortName.value = customer.shortName?.trim() || '';
|
||||
// 清空联系人和商机
|
||||
form.value.contactId = undefined;
|
||||
form.value.contactName = '';
|
||||
@@ -963,6 +968,39 @@ const handleOwnerChange = (userId: string | number | undefined) => {
|
||||
form.value.ownerUserName = user?.nickName || ''
|
||||
}
|
||||
|
||||
/** 当前登录用户 ID(后端 current-user 返回 id,兼容历史 userId 字段) */
|
||||
const resolveCurrentUserId = (): string => {
|
||||
const info = userStore.info || ({} as Api.Auth.UserInfo & Record<string, unknown>)
|
||||
const fromInfo = String(info.id || info.userId || '')
|
||||
if (fromInfo) return fromInfo
|
||||
const token = userStore.accessToken
|
||||
if (!token || token.split('.').length < 2) return ''
|
||||
try {
|
||||
const payload = token.split('.')[1]
|
||||
const b64 = payload.replace(/-/g, '+').replace(/_/g, '/')
|
||||
const pad = '='.repeat((4 - (b64.length % 4)) % 4)
|
||||
const obj = JSON.parse(atob(b64 + pad))
|
||||
return String(obj?.sub || '')
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/** 新增时默认负责人为当前登录人 */
|
||||
const applyDefaultOwnerUser = () => {
|
||||
const currentUserId = resolveCurrentUserId()
|
||||
if (!currentUserId) return
|
||||
const matched = userList.value.find((item) => String(item.userId) === currentUserId)
|
||||
if (matched) {
|
||||
form.value.ownerUserId = matched.userId as QuoteForm['ownerUserId']
|
||||
form.value.ownerUserName = matched.nickName || ''
|
||||
return
|
||||
}
|
||||
form.value.ownerUserId = currentUserId as QuoteForm['ownerUserId']
|
||||
const info = userStore.info as Record<string, string | undefined>
|
||||
form.value.ownerUserName = info?.realName || info?.userName || info?.username || ''
|
||||
}
|
||||
|
||||
const formatApproverOptionLabel = (user: { nickName?: string; userName?: string }) => {
|
||||
const nick = (user.nickName || '').trim()
|
||||
const account = (user.userName || '').trim()
|
||||
@@ -1610,6 +1648,7 @@ const initNew = async () => {
|
||||
form.value.quoteDate = getTodayDate();
|
||||
await dictStore.loadAllDict();
|
||||
await Promise.all([loadSupplierList(), loadUserList()]);
|
||||
applyDefaultOwnerUser();
|
||||
};
|
||||
|
||||
const initEdit = async (id: string | number) => {
|
||||
@@ -1674,11 +1713,27 @@ onMounted(async () => {
|
||||
getQuoteInfoForCopy(copyId);
|
||||
} else {
|
||||
form.value.quoteDate = getTodayDate();
|
||||
applyDefaultOwnerUser();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.quote-form-body {
|
||||
:deep(.el-form-item__label) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.quote-section-title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: var(--primary-dark, #059669);
|
||||
}
|
||||
|
||||
.box-card {
|
||||
margin-top: 10px;
|
||||
}
|
||||
@@ -1708,17 +1763,19 @@ onMounted(async () => {
|
||||
.quote-type-item :deep(.el-form-item__content) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.quote-type-item :deep(.el-radio-group) {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.quote-type-item :deep(.el-radio-button__inner) {
|
||||
padding: 14px 26px;
|
||||
font-size: 16px;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.quote-type-wrapper {
|
||||
@@ -2027,11 +2084,6 @@ onMounted(async () => {
|
||||
gap: 8px 12px;
|
||||
}
|
||||
|
||||
.quote-type-item :deep(.el-radio-group) {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.approval-dialog-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -438,8 +438,7 @@
|
||||
ref="submitFormRef"
|
||||
:model="submitForm"
|
||||
:rules="submitRules"
|
||||
label-position="right"
|
||||
label-width="100px"
|
||||
label-position="top"
|
||||
class="crm-dialog-form"
|
||||
scroll-to-error
|
||||
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
|
||||
@@ -1772,8 +1771,34 @@ const DEFAULT_QUOTE_TABLE_REMARK_ROW = `<tr>
|
||||
<td colspan="10" style="padding: 8px; border: 1px solid #000; background-color: #f9f9f9; text-align: center; font-size: 13px;"> </td>
|
||||
</tr>`;
|
||||
|
||||
/** 生成可替换的产品明细区块(产品行 + 合计行,每次从数据库数据渲染) */
|
||||
const buildQuoteDynamicProductSection = (quote: QuoteVO): string => {
|
||||
const QUOTE_TABLE_REMARK_ROW_PATTERN =
|
||||
/<!--\s*备注行\s*-->\s*(<tr[\s\S]*?colspan=["']10["'][\s\S]*?<\/tr>)/i;
|
||||
|
||||
/** 从已保存模板中提取备注行(兼容备注行在合计上方或下方的旧结构) */
|
||||
const extractQuoteTableRemarkRow = (html: string): string => {
|
||||
const withComment = html.match(QUOTE_TABLE_REMARK_ROW_PATTERN);
|
||||
if (withComment?.[1]) return withComment[1];
|
||||
|
||||
const colspan10Rows = [...html.matchAll(/<tr[\s\S]*?colspan=["']10["'][\s\S]*?<\/tr>/gi)].map((m) => m[0]);
|
||||
for (let i = colspan10Rows.length - 1; i >= 0; i--) {
|
||||
const row = colspan10Rows[i];
|
||||
if (!row.includes('合计') && !row.includes('暂无产品信息')) {
|
||||
return row;
|
||||
}
|
||||
}
|
||||
return DEFAULT_QUOTE_TABLE_REMARK_ROW;
|
||||
};
|
||||
|
||||
/** 移除动态区块外重复的备注行(旧模板中备注行在合计下方时) */
|
||||
const removeOrphanRemarkRowAfterDynamicSection = (html: string): string => {
|
||||
return html.replace(
|
||||
/(<!--\s*quote-dynamic-products-end\s*-->)\s*(?:<!--\s*备注行\s*-->\s*)?<tr[\s\S]*?colspan=["']10["'][\s\S]*?<\/tr>/i,
|
||||
'$1'
|
||||
);
|
||||
};
|
||||
|
||||
/** 生成可替换的产品明细区块(产品行 + 备注行 + 合计行,每次从数据库数据渲染) */
|
||||
const buildQuoteDynamicProductSection = (quote: QuoteVO, remarkRow: string = DEFAULT_QUOTE_TABLE_REMARK_ROW): string => {
|
||||
const productRows =
|
||||
quote.productList
|
||||
?.map(
|
||||
@@ -1805,6 +1830,8 @@ const buildQuoteDynamicProductSection = (quote: QuoteVO): string => {
|
||||
|
||||
return `<!-- quote-dynamic-products-start -->
|
||||
${productRows}
|
||||
<!-- 备注行 -->
|
||||
${remarkRow}
|
||||
${totalRow}
|
||||
<!-- quote-dynamic-products-end -->`;
|
||||
};
|
||||
@@ -1815,24 +1842,21 @@ const applyQuoteProductDataToTemplate = (
|
||||
quote: QuoteVO,
|
||||
supplierInfo?: SupplierInfoVO
|
||||
): string => {
|
||||
const dynamicSection = buildQuoteDynamicProductSection(quote);
|
||||
const remarkRow = extractQuoteTableRemarkRow(savedTemplate);
|
||||
const dynamicSection = buildQuoteDynamicProductSection(quote, remarkRow);
|
||||
let result = savedTemplate;
|
||||
if (TEMPLATE_DYNAMIC_PRODUCTS_PATTERN.test(savedTemplate)) {
|
||||
return savedTemplate.replace(TEMPLATE_DYNAMIC_PRODUCTS_PATTERN, dynamicSection);
|
||||
}
|
||||
|
||||
if (!/<tbody[\s\S]*?<\/tbody>/i.test(savedTemplate)) {
|
||||
result = savedTemplate.replace(TEMPLATE_DYNAMIC_PRODUCTS_PATTERN, dynamicSection);
|
||||
} else if (!/<tbody[\s\S]*?<\/tbody>/i.test(savedTemplate)) {
|
||||
return generateQuoteTemplate(quote, supplierInfo);
|
||||
}
|
||||
|
||||
return savedTemplate.replace(/<tbody>([\s\S]*?)<\/tbody>/i, (_match, body: string) => {
|
||||
const remarkRow =
|
||||
body.match(/<tr>[\s\S]*?colspan=["']10["'][\s\S]*?<\/tr>/i)?.[0] || DEFAULT_QUOTE_TABLE_REMARK_ROW;
|
||||
return `<tbody>
|
||||
} else {
|
||||
result = savedTemplate.replace(/<tbody>([\s\S]*?)<\/tbody>/i, () => {
|
||||
return `<tbody>
|
||||
${dynamicSection}
|
||||
<!-- 备注行 -->
|
||||
${remarkRow}
|
||||
</tbody>`;
|
||||
});
|
||||
});
|
||||
}
|
||||
return removeOrphanRemarkRowAfterDynamicSection(result);
|
||||
};
|
||||
|
||||
/** 生成报价单HTML模版 */
|
||||
@@ -1939,8 +1963,6 @@ const generateQuoteTemplate = (quote: QuoteVO, supplierInfo?: SupplierInfoVO): s
|
||||
</thead>
|
||||
<tbody>
|
||||
${dynamicProductSection}
|
||||
<!-- 备注行 -->
|
||||
${DEFAULT_QUOTE_TABLE_REMARK_ROW}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -264,7 +264,7 @@
|
||||
:limit="5"
|
||||
accept="image/*,.pdf"
|
||||
folder="pl/carrier/license"
|
||||
hint="支持 JPG、PNG、PDF,单文件不超过 20MB,最多 5 个"
|
||||
hint="支持 JPG、PNG、PDF,最多 5 个"
|
||||
@update:model-value="handleCertFieldChange('businessLicenseUrls')"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -275,7 +275,7 @@
|
||||
:limit="5"
|
||||
accept="image/*,.pdf"
|
||||
folder="pl/carrier/permit"
|
||||
hint="支持 JPG、PNG、PDF,单文件不超过 20MB,最多 5 个"
|
||||
hint="支持 JPG、PNG、PDF,最多 5 个"
|
||||
@update:model-value="handleCertFieldChange('roadTransportLicenseUrls')"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -286,7 +286,7 @@
|
||||
:limit="5"
|
||||
accept="image/*,.pdf"
|
||||
folder="pl/carrier/agreement"
|
||||
hint="支持 JPG、PNG、PDF,单文件不超过 20MB,最多 5 个"
|
||||
hint="支持 JPG、PNG、PDF,最多 5 个"
|
||||
@update:model-value="handleCertFieldChange('transportAgreementUrls')"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
@@ -6,7 +6,7 @@ APP_NAME=山峡化工一体化门户
|
||||
DEBUG=false
|
||||
|
||||
DEFAULT_TENANT_NO=shanxia2.0
|
||||
WEB_AUTH_DEFAULT_ROLE=super_admin
|
||||
WEB_AUTH_DEFAULT_ROLE=admin
|
||||
|
||||
# === 与本地一致的远程中间件(按实际 .env 填写)===
|
||||
DATABASE_URL=mysql+pymysql://USER:PASSWORD@47.99.164.66:3306/shanxia2.0
|
||||
|
||||
Reference in new issue
Block a user