6-27-晚上提交,修复业务员物流信息展示问题
This commit is contained in:
parent
d53bf0b536
commit
a591b180de
@ -355,7 +355,7 @@ def get_logistics_trace(
|
||||
order_id: int, # 订单ID
|
||||
logistics_service: LogisticsService = Depends(get_logistics_service), # 物流服务实例
|
||||
session: Session = Depends(get_db_session), # 数据库会话
|
||||
current_user: dict = Depends(require_roles("manager", "admin", "driver")), # 当前登录用户
|
||||
current_user: dict = Depends(require_roles("manager", "admin", "driver", "salesman")), # 当前登录用户
|
||||
_permission_user: dict = Depends(require_permissions("logistics:trace:list")), # 权限校验:轨迹查看权限
|
||||
) -> dict:
|
||||
"""
|
||||
@ -369,7 +369,7 @@ def get_logistics_trace(
|
||||
返回值:
|
||||
物流轨迹列表,按时间倒序排列
|
||||
|
||||
权限要求:manager、admin、driver 角色,需 logistics:trace:list 权限
|
||||
权限要求:manager、admin、driver、salesman 角色,需 logistics:trace:list 权限
|
||||
"""
|
||||
return success_payload(logistics_service.get_trace(order_id, session, current_user))
|
||||
|
||||
|
||||
@ -78,6 +78,7 @@ class AuthService:
|
||||
"customer:list",
|
||||
"reminder:list",
|
||||
"ai:parse-order",
|
||||
"logistics:trace:list",
|
||||
],
|
||||
"manager": [
|
||||
"order:list",
|
||||
|
||||
@ -167,6 +167,7 @@ DEFAULT_ROLE_MENU_CODES = {
|
||||
"customer:list",
|
||||
"reminder:list",
|
||||
"ai:parse-order",
|
||||
"logistics:trace:list",
|
||||
],
|
||||
"manager": [
|
||||
"order:list",
|
||||
|
||||
@ -666,7 +666,11 @@ class LogisticsService:
|
||||
}
|
||||
for item in traces
|
||||
]
|
||||
try:
|
||||
third_party_trace_list = self._load_third_party_traces(order_id, session)
|
||||
except AppException as exc:
|
||||
logger.warning("[get_trace] 第三方轨迹查询失败,降级返回内部轨迹: %s", exc)
|
||||
third_party_trace_list = []
|
||||
if trace_list or third_party_trace_list:
|
||||
return {
|
||||
"order_id": order_id,
|
||||
@ -1106,6 +1110,11 @@ class LogisticsService:
|
||||
if task is None or task.driver_id != current_user.get("user_id"):
|
||||
raise AppException(code=ErrorCode.FORBIDDEN, message="仅可访问本人订单轨迹", status_code=403)
|
||||
return
|
||||
if role_code == "salesman":
|
||||
order = self.order_repository.get_order(session, order_id)
|
||||
if order is None or order.salesman_id != current_user.get("user_id"):
|
||||
raise AppException(code=ErrorCode.FORBIDDEN, message="仅可访问本人订单轨迹", status_code=403)
|
||||
return
|
||||
raise AppException(code=ErrorCode.FORBIDDEN, message="无权限访问", status_code=403)
|
||||
|
||||
def _notify_driver_task_assigned(self, session: Session, task, order) -> None:
|
||||
@ -1257,12 +1266,11 @@ class LogisticsService:
|
||||
session,
|
||||
{
|
||||
"reminder_type": "order_status_change",
|
||||
"receiver_id": receiver,
|
||||
"title": "订单状态变更",
|
||||
"content": reminder_content,
|
||||
"receiver_user_id": receiver,
|
||||
"reminder_title": "订单状态变更",
|
||||
"reminder_content": reminder_content,
|
||||
"biz_type": "sales_order",
|
||||
"biz_id": order.id,
|
||||
"source_platform": "backend",
|
||||
},
|
||||
)
|
||||
|
||||
@ -1481,6 +1489,7 @@ class LogisticsService:
|
||||
AppException: 配置不完整或 API 调用失败时抛出
|
||||
"""
|
||||
import hashlib
|
||||
from urllib.parse import urlencode as _urlencode
|
||||
|
||||
settings = self.settings
|
||||
if not settings.kuaidi100_key or not settings.kuaidi100_customer:
|
||||
@ -1490,36 +1499,33 @@ class LogisticsService:
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
# 快递100 传统查询接口(GET 方式)
|
||||
endpoint = (settings.logistics_trace_endpoint or "https://api.kuaidi100.com").strip().rstrip("/")
|
||||
path = (settings.logistics_trace_path or "/query").strip()
|
||||
path = (settings.logistics_trace_path or "/api").strip()
|
||||
if not path.startswith("/"):
|
||||
path = f"/{path}"
|
||||
url = f"{endpoint}{path}"
|
||||
|
||||
param = {
|
||||
"com": express_company,
|
||||
"num": tracking_number,
|
||||
"phone": "",
|
||||
"from": "",
|
||||
"to": "",
|
||||
}
|
||||
# 快递100 签名:MD5(key + 排序参数拼接 + customer + key)
|
||||
sorted_params = "".join(f"{k}{v}" for k, v in sorted(param.items()) if v)
|
||||
sign_str = f"{settings.kuaidi100_key}{sorted_params}{settings.kuaidi100_customer}{settings.kuaidi100_key}"
|
||||
# 签名:MD5(key + 运单号 + 快递公司编码 + customer)
|
||||
sign_str = f"{settings.kuaidi100_key}{tracking_number}{express_company}{settings.kuaidi100_customer}"
|
||||
sign = hashlib.md5(sign_str.encode("utf-8")).hexdigest()
|
||||
|
||||
body = json.dumps({
|
||||
"customer": settings.kuaidi100_customer,
|
||||
params = {
|
||||
"key": settings.kuaidi100_key,
|
||||
"com": express_company,
|
||||
"nu": tracking_number,
|
||||
"show": "0",
|
||||
"muti": "1",
|
||||
"order": "desc",
|
||||
"customer": settings.kuaidi100_customer,
|
||||
"sign": sign,
|
||||
"param": param,
|
||||
}, ensure_ascii=False).encode("utf-8")
|
||||
}
|
||||
url = f"{url}?{_urlencode(params)}"
|
||||
|
||||
req = request.Request(
|
||||
url=url,
|
||||
data=body,
|
||||
headers={"Content-Type": "application/json; charset=UTF-8"},
|
||||
method="POST",
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
method="GET",
|
||||
)
|
||||
try:
|
||||
with request.urlopen(req, timeout=20) as response:
|
||||
@ -1608,10 +1614,10 @@ class LogisticsService:
|
||||
third_party_trace_list: 第三方轨迹列表
|
||||
|
||||
Returns:
|
||||
合并后的轨迹列表,按 node_time 升序排列
|
||||
合并后的轨迹列表,按 node_time 降序排列(最新的在前)
|
||||
"""
|
||||
merged = list(local_trace_list) + list(third_party_trace_list)
|
||||
merged.sort(key=lambda item: item.get("node_time") or "")
|
||||
merged.sort(key=lambda item: item.get("node_time") or "", reverse=True)
|
||||
return merged
|
||||
|
||||
|
||||
|
||||
@ -311,6 +311,7 @@ def seed_menus(db_session):
|
||||
RoleMenu(role_id=2, menu_id=22), # customer:create
|
||||
RoleMenu(role_id=2, menu_id=23), # customer:update
|
||||
RoleMenu(role_id=2, menu_id=45), # ai:parse-order
|
||||
RoleMenu(role_id=2, menu_id=28), # logistics:trace:list
|
||||
# 管理层:订单、审批、物流、报表、主数据、AI
|
||||
RoleMenu(role_id=3, menu_id=1), # order:list
|
||||
RoleMenu(role_id=3, menu_id=3), # order:approve
|
||||
|
||||
@ -130,13 +130,17 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{taskInfo && taskInfo.status !== 'delivered' && taskInfo.status !== 'canceled'}}" class="action-bar">
|
||||
<view wx:if="{{taskInfo && taskInfo.status !== 'delivered' && taskInfo.status !== 'canceled' && taskInfo.status !== 'picked_up'}}" class="action-bar">
|
||||
<input wx:if="{{taskInfo.status !== 'pending'}}" class="remark-input" placeholder="备注(可选)" value="{{actionRemark}}" bindinput="handleRemarkInput" />
|
||||
<button class="action-btn primary-btn" type="primary" loading="{{actionLoading}}" bindtap="handleAction">
|
||||
{{ taskInfo.status === 'pending' ? '接受任务' : (taskInfo.status === 'accepted' ? '确认揽货' : '确认送达') }}
|
||||
{{ taskInfo.status === 'pending' ? '接受任务' : '确认揽货' }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{taskInfo && taskInfo.status === 'picked_up'}}" class="action-bar done-bar">
|
||||
<view class="done-label">揽件完成,等待业务员绑定物流单号</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{taskInfo && taskInfo.status === 'delivered'}}" class="action-bar done-bar">
|
||||
<view class="done-label">任务已完成</view>
|
||||
</view>
|
||||
|
||||
@ -507,7 +507,20 @@
|
||||
<!-- 自发订单提示 -->
|
||||
<div v-if="detail.order.trackingNumber" class="approval-hint">
|
||||
马甸工厂自发 — 已填写快递单号({{ detail.order.trackingNumber }}),审批通过后跳过工厂下发
|
||||
<button v-if="canEditTrackingNumber(detail.order.rawStatus)" class="link-btn" @click="openSelfTrackingEdit" style="margin-left: 8px; font-size: 12px;">修改单号</button>
|
||||
</div>
|
||||
<!-- 自发订单物流轨迹 -->
|
||||
<div v-if="detail.order.trackingNumber && detail.traceList.length" class="doc-trace-timeline" style="margin-top: 12px;">
|
||||
<div v-for="(item, index) in detail.traceList" :key="index" class="doc-trace-item">
|
||||
<div class="doc-trace-dot" :class="{ active: index === 0 }"></div>
|
||||
<div v-if="index < detail.traceList.length - 1" class="doc-trace-line"></div>
|
||||
<div class="doc-trace-card">
|
||||
<span class="doc-trace-time">{{ item.node_time }}</span>
|
||||
<span class="doc-trace-desc">{{ item.node_desc }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else-if="detail.order.trackingNumber" class="doc-empty-hint" style="margin-top: 8px;">暂无物流轨迹</p>
|
||||
|
||||
<!-- 下发工厂选择(非自发订单) -->
|
||||
<div v-if="!detail.order.trackingNumber" class="approval-factory">
|
||||
@ -897,6 +910,7 @@ import {
|
||||
generateSupplierText,
|
||||
fetchProductOptions,
|
||||
cancelApproveOrder,
|
||||
approveOrder,
|
||||
updateOrderTracking,
|
||||
updateLogisticsTracking,
|
||||
batchMatchLogisticsPreview,
|
||||
@ -1445,7 +1459,7 @@ async function loadDetail() {
|
||||
};
|
||||
detail.items = d.items || [];
|
||||
detail.approveLogs = d.approve_logs || [];
|
||||
detail.logistics = d.logistics_info?.task || null;
|
||||
detail.logistics = d.logistics_info?.task?.task_id ? d.logistics_info.task : null;
|
||||
|
||||
// 物流轨迹:有物流任务单号或订单单号都加载
|
||||
detail.traceList = [];
|
||||
@ -1633,11 +1647,11 @@ async function handleApprovalPass() {
|
||||
|
||||
// 订单审批通过
|
||||
if (detail.order.trackingNumber) {
|
||||
// 自发订单:直接完成(跳过工厂和司机)
|
||||
// 自发订单:调用审批接口,后端会自动转到 in_transit
|
||||
detailActionBusy.value = true;
|
||||
try {
|
||||
await changeOrderStatus(detail.orderId, {target_status: 'completed'});
|
||||
toast.success('马甸工厂自发 — 审批通过,订单已直接完成');
|
||||
await approveOrder(detail.orderId, {approve_result: 'pass', approve_opinion: '审批通过'});
|
||||
toast.success('马甸工厂自发 — 审批通过,订单已进入运输中');
|
||||
await loadDetail();
|
||||
await handleSearch();
|
||||
} catch (e) {
|
||||
|
||||
@ -91,6 +91,16 @@
|
||||
<span class="field-value">{{ selfDeliveryTracking }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 物流轨迹 -->
|
||||
<div v-if="traceList.length" class="trace-timeline">
|
||||
<div v-for="(item, index) in traceList" :key="index" class="trace-item">
|
||||
<div class="trace-dot" :class="{ active: index === 0 }"></div>
|
||||
<div class="trace-content">
|
||||
<span class="trace-time">{{ item.node_time }}</span>
|
||||
<span class="trace-desc">{{ item.node_desc }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- 4. 物流信息 -->
|
||||
<section v-if="trackingInfo" class="waterfall-item">
|
||||
@ -326,9 +336,13 @@ async function loadDetail() {
|
||||
async function loadTrace() {
|
||||
try {
|
||||
const orderId = route.params.id;
|
||||
const result = await fetchOrderLogisticsTrace(orderId);
|
||||
if (result) {
|
||||
traceList.value = [{ node_time: result.latest_time, node_desc: result.latest_desc }];
|
||||
const token = localStorage.getItem(SALES_TOKEN_KEY) || "";
|
||||
const res = await fetch(`${apiBaseUrl}/api/logistics/${orderId}/trace`, {
|
||||
headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}) },
|
||||
});
|
||||
const result = await res.json().catch(() => ({}));
|
||||
if (res.ok && result.code === 0) {
|
||||
traceList.value = (result.data || {}).trace_list || [];
|
||||
}
|
||||
} catch {
|
||||
// 忽略错误
|
||||
|
||||
Loading…
Reference in New Issue
Block a user