fix(orders): wire detail progress timeline and two-step cancel
Order progress was stuck on "订单创建" because timeline only read sparse timestamp columns and status transitions didn't always stamp them. Drive timeline from current status + related records, backfill lifecycle timestamps on update_status, and replace window.confirm cancel with a 关闭订单 → 确认取消 button step in the detail modal.
This commit is contained in:
1 parent
64d0dc66da
commit
ad40d9a7ca
4 files changed
+327
-61
No files matched your search
@@ -637,7 +637,8 @@
|
|||||||
var footer = "";
|
var footer = "";
|
||||||
// Status-aware footer. Top-right ✕ is the only generic dismiss control.
|
// Status-aware footer. Top-right ✕ is the only generic dismiss control.
|
||||||
if (actions.can_cancel) {
|
if (actions.can_cancel) {
|
||||||
footer += '<button type="button" class="btn-ghost" data-aksrt-order-cancel="' + escapeHtml(order.order_number) + '">关闭订单</button>';
|
// Two-step cancel inside the modal — no window.confirm.
|
||||||
|
footer += '<button type="button" class="btn-ghost" data-aksrt-order-cancel="' + escapeHtml(order.order_number) + '" data-aksrt-cancel-step="1">关闭订单</button>';
|
||||||
}
|
}
|
||||||
if (actions.can_receipt) {
|
if (actions.can_receipt) {
|
||||||
footer += '<button type="button" class="btn-primary" data-aksrt-order-receipt="' + escapeHtml(order.order_number) + '">确认收货</button>';
|
footer += '<button type="button" class="btn-primary" data-aksrt-order-receipt="' + escapeHtml(order.order_number) + '">确认收货</button>';
|
||||||
@@ -919,21 +920,51 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel
|
// Cancel — two-step in detail modal (关闭订单 → 确认取消), list card same.
|
||||||
var cancelButton = event.target.closest("[data-aksrt-order-cancel]");
|
var cancelButton = event.target.closest("[data-aksrt-order-cancel]");
|
||||||
if (cancelButton) {
|
if (cancelButton) {
|
||||||
var cancelOrderNumber = cancelButton.getAttribute("data-aksrt-order-cancel");
|
var cancelOrderNumber = cancelButton.getAttribute("data-aksrt-order-cancel");
|
||||||
if (!window.confirm("确认关闭该订单?关闭后不可再支付。")) {
|
if (!cancelOrderNumber) return;
|
||||||
|
|
||||||
|
// First click: transform into "确认取消" (no browser confirm dialog).
|
||||||
|
var step = cancelButton.getAttribute("data-aksrt-cancel-step") || "1";
|
||||||
|
if (step !== "2") {
|
||||||
|
cancelButton.setAttribute("data-aksrt-cancel-step", "2");
|
||||||
|
cancelButton.classList.remove("btn-ghost");
|
||||||
|
cancelButton.classList.add("btn-primary", "is-cancel-confirm");
|
||||||
|
cancelButton.textContent = "确认取消";
|
||||||
|
// Auto-reset if user doesn't confirm within a few seconds.
|
||||||
|
if (cancelButton._aksrtCancelReset) {
|
||||||
|
window.clearTimeout(cancelButton._aksrtCancelReset);
|
||||||
|
}
|
||||||
|
cancelButton._aksrtCancelReset = window.setTimeout(function () {
|
||||||
|
if (!cancelButton.isConnected) return;
|
||||||
|
if (cancelButton.getAttribute("data-aksrt-cancel-step") !== "2") return;
|
||||||
|
cancelButton.setAttribute("data-aksrt-cancel-step", "1");
|
||||||
|
cancelButton.classList.add("btn-ghost");
|
||||||
|
cancelButton.classList.remove("btn-primary", "is-cancel-confirm");
|
||||||
|
// Detail modal uses "关闭订单"; list cards use "取消".
|
||||||
|
var inDetail = !!(detailModal && detailModal.contains(cancelButton));
|
||||||
|
cancelButton.textContent = inDetail ? "关闭订单" : "取消";
|
||||||
|
}, 5000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var cancelLabel = cancelButton.textContent || "取消";
|
|
||||||
|
if (cancelButton._aksrtCancelReset) {
|
||||||
|
window.clearTimeout(cancelButton._aksrtCancelReset);
|
||||||
|
cancelButton._aksrtCancelReset = null;
|
||||||
|
}
|
||||||
cancelButton.disabled = true;
|
cancelButton.disabled = true;
|
||||||
cancelButton.textContent = "取消中...";
|
cancelButton.textContent = "取消中...";
|
||||||
request(orderPath(cancelOrderNumber, "/cancel"))
|
request(orderPath(cancelOrderNumber, "/cancel"))
|
||||||
.then(function (result) {
|
.then(function (result) {
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
cancelButton.disabled = false;
|
cancelButton.disabled = false;
|
||||||
cancelButton.textContent = cancelLabel;
|
cancelButton.setAttribute("data-aksrt-cancel-step", "1");
|
||||||
|
cancelButton.classList.add("btn-ghost");
|
||||||
|
cancelButton.classList.remove("btn-primary", "is-cancel-confirm");
|
||||||
|
var inDetailFail = !!(detailModal && detailModal.contains(cancelButton));
|
||||||
|
cancelButton.textContent = inDetailFail ? "关闭订单" : "取消";
|
||||||
showToast(result.message || "取消失败。");
|
showToast(result.message || "取消失败。");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -941,12 +972,22 @@
|
|||||||
var card = findOrderCard(cancelOrderNumber);
|
var card = findOrderCard(cancelOrderNumber);
|
||||||
updateCardStatus(card, "cancelled", "已取消");
|
updateCardStatus(card, "cancelled", "已取消");
|
||||||
if (detailOrderNumber === cancelOrderNumber) {
|
if (detailOrderNumber === cancelOrderNumber) {
|
||||||
closeOrderDetailModal();
|
// Refresh modal body with cancelled timeline, then close shortly.
|
||||||
|
if (result.data && result.data.order) {
|
||||||
|
renderOrderDetail(result.data.order);
|
||||||
|
}
|
||||||
|
window.setTimeout(function () {
|
||||||
|
closeOrderDetailModal();
|
||||||
|
}, 600);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(function () {
|
.catch(function () {
|
||||||
cancelButton.disabled = false;
|
cancelButton.disabled = false;
|
||||||
cancelButton.textContent = cancelLabel;
|
cancelButton.setAttribute("data-aksrt-cancel-step", "1");
|
||||||
|
cancelButton.classList.add("btn-ghost");
|
||||||
|
cancelButton.classList.remove("btn-primary", "is-cancel-confirm");
|
||||||
|
var inDetailErr = !!(detailModal && detailModal.contains(cancelButton));
|
||||||
|
cancelButton.textContent = inDetailErr ? "关闭订单" : "取消";
|
||||||
showToast("网络错误,请稍后重试。");
|
showToast("网络错误,请稍后重试。");
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -204,21 +204,48 @@ trait OrderRepositoryCore {
|
|||||||
$update_data = ['status' => $new_status];
|
$update_data = ['status' => $new_status];
|
||||||
$format = ['%s'];
|
$format = ['%s'];
|
||||||
|
|
||||||
|
// Stamp lifecycle timestamps. Some statuses share a column (e.g. expired → cancelled_at;
|
||||||
|
// pending_review still counts as a completion-like milestone for the timeline).
|
||||||
$time_fields = [
|
$time_fields = [
|
||||||
'paid' => 'paid_at',
|
\AKSRT\Core\DomainModel::ORDER_STATUS_PAID => 'paid_at',
|
||||||
'delivered' => 'delivered_at',
|
\AKSRT\Core\DomainModel::ORDER_STATUS_DELIVERED => 'delivered_at',
|
||||||
'completed' => 'completed_at',
|
\AKSRT\Core\DomainModel::ORDER_STATUS_COMPLETED => 'completed_at',
|
||||||
'cancelled' => 'cancelled_at',
|
\AKSRT\Core\DomainModel::ORDER_STATUS_PENDING_REVIEW => 'completed_at',
|
||||||
'refunded' => 'refunded_at',
|
\AKSRT\Core\DomainModel::ORDER_STATUS_CANCELLED => 'cancelled_at',
|
||||||
|
\AKSRT\Core\DomainModel::ORDER_STATUS_EXPIRED => 'cancelled_at',
|
||||||
|
\AKSRT\Core\DomainModel::ORDER_STATUS_REFUNDED => 'refunded_at',
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($time_fields as $status_key => $field) {
|
if (isset($time_fields[$new_status])) {
|
||||||
if ($new_status === $status_key) {
|
$field = $time_fields[$new_status];
|
||||||
|
// Do not overwrite an existing timestamp (idempotent callbacks / force updates).
|
||||||
|
$existing = is_string($order[$field] ?? null) ? trim((string) $order[$field]) : '';
|
||||||
|
if ($existing === '' || str_starts_with($existing, '0000-00-00')) {
|
||||||
$update_data[$field] = current_time('mysql');
|
$update_data[$field] = current_time('mysql');
|
||||||
$format[] = '%s';
|
$format[] = '%s';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When jumping past paid (force admin transitions), backfill paid_at so the
|
||||||
|
// progress chain is never stuck on "订单创建" alone.
|
||||||
|
if (
|
||||||
|
in_array($new_status, [
|
||||||
|
\AKSRT\Core\DomainModel::ORDER_STATUS_DELIVERED,
|
||||||
|
\AKSRT\Core\DomainModel::ORDER_STATUS_COMPLETED,
|
||||||
|
\AKSRT\Core\DomainModel::ORDER_STATUS_PENDING_REVIEW,
|
||||||
|
\AKSRT\Core\DomainModel::ORDER_STATUS_REFUNDING,
|
||||||
|
\AKSRT\Core\DomainModel::ORDER_STATUS_REFUNDED,
|
||||||
|
], true)
|
||||||
|
) {
|
||||||
|
$existing_paid = is_string($order['paid_at'] ?? null) ? trim((string) $order['paid_at']) : '';
|
||||||
|
if ($existing_paid === '' || str_starts_with($existing_paid, '0000-00-00')) {
|
||||||
|
if (!isset($update_data['paid_at'])) {
|
||||||
|
$update_data['paid_at'] = current_time('mysql');
|
||||||
|
$format[] = '%s';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!empty($extra['payment_method'])) {
|
if (!empty($extra['payment_method'])) {
|
||||||
$update_data['payment_method'] = sanitize_key($extra['payment_method']);
|
$update_data['payment_method'] = sanitize_key($extra['payment_method']);
|
||||||
$format[] = '%s';
|
$format[] = '%s';
|
||||||
|
|||||||
@@ -490,47 +490,228 @@ class UserOrders {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Build chronological order progress for the detail modal.
|
||||||
|
*
|
||||||
|
* Driven by current status + available timestamps / related records so the
|
||||||
|
* chain advances even when historical rows lack paid_at/delivered_at.
|
||||||
|
*
|
||||||
* @param array<string, mixed> $order
|
* @param array<string, mixed> $order
|
||||||
* @param array<int, array> $shipping_records
|
* @param array<int, array> $shipping_records
|
||||||
* @param array<int, array> $after_sale_records
|
* @param array<int, array> $after_sale_records
|
||||||
* @return array<int, array{time:string,label:string}>
|
* @return array<int, array{time:string,label:string,key?:string}>
|
||||||
*/
|
*/
|
||||||
private function build_order_timeline(array $order, array $shipping_records, array $after_sale_records): array {
|
private function build_order_timeline(array $order, array $shipping_records, array $after_sale_records): array {
|
||||||
$timeline = [];
|
$status = sanitize_key((string) ($order['status'] ?? Order::STATUS_PENDING));
|
||||||
$date_fields = [
|
$created_at = $this->timeline_time((string) ($order['created_at'] ?? ''));
|
||||||
'created_at' => __('订单创建', 'aksrt'),
|
$paid_at = $this->timeline_time((string) ($order['paid_at'] ?? ''));
|
||||||
'paid_at' => __('完成支付', 'aksrt'),
|
$delivered_at = $this->timeline_time((string) ($order['delivered_at'] ?? ''));
|
||||||
'delivered_at' => __('订单发货', 'aksrt'),
|
$completed_at = $this->timeline_time((string) ($order['completed_at'] ?? ''));
|
||||||
'completed_at' => __('交易完成', 'aksrt'),
|
$cancelled_at = $this->timeline_time((string) ($order['cancelled_at'] ?? ''));
|
||||||
'cancelled_at' => __('订单取消', 'aksrt'),
|
$refunded_at = $this->timeline_time((string) ($order['refunded_at'] ?? ''));
|
||||||
'refunded_at' => __('完成退款', 'aksrt'),
|
|
||||||
];
|
$shipping_created = '';
|
||||||
foreach ($date_fields as $key => $label) {
|
foreach ($shipping_records as $record) {
|
||||||
if (!empty($order[$key])) {
|
$t = $this->timeline_time((string) ($record['created_at'] ?? ''));
|
||||||
$timeline[] = [
|
if ($t !== '' && ($shipping_created === '' || strcmp($t, $shipping_created) < 0)) {
|
||||||
'time' => (string) $order[$key],
|
$shipping_created = $t;
|
||||||
'label' => $label,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach ($shipping_records as $record) {
|
if ($delivered_at === '' && $shipping_created !== '') {
|
||||||
$timeline[] = [
|
$delivered_at = $shipping_created;
|
||||||
'time' => (string) ($record['created_at'] ?? ''),
|
}
|
||||||
'label' => sprintf(__('物流更新:%s', 'aksrt'), $record['tracking_no'] ?? ''),
|
|
||||||
|
$paid_like = [
|
||||||
|
Order::STATUS_PAID,
|
||||||
|
Order::STATUS_DELIVERED,
|
||||||
|
Order::STATUS_COMPLETED,
|
||||||
|
Order::STATUS_PENDING_REVIEW,
|
||||||
|
Order::STATUS_REFUNDING,
|
||||||
|
Order::STATUS_REFUNDED,
|
||||||
|
];
|
||||||
|
$ship_like = [
|
||||||
|
Order::STATUS_DELIVERED,
|
||||||
|
Order::STATUS_COMPLETED,
|
||||||
|
Order::STATUS_PENDING_REVIEW,
|
||||||
|
];
|
||||||
|
$done_like = [
|
||||||
|
Order::STATUS_COMPLETED,
|
||||||
|
Order::STATUS_PENDING_REVIEW,
|
||||||
|
];
|
||||||
|
|
||||||
|
$timeline = [];
|
||||||
|
$push = static function (array &$out, string $key, string $label, string $time): void {
|
||||||
|
if ($label === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$out[] = [
|
||||||
|
'key' => $key,
|
||||||
|
'label' => $label,
|
||||||
|
'time' => $time,
|
||||||
];
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1) Always: order created.
|
||||||
|
$push(
|
||||||
|
$timeline,
|
||||||
|
'created',
|
||||||
|
__('订单创建', 'aksrt'),
|
||||||
|
$created_at !== '' ? $created_at : ''
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2) Paid (status-driven; fall back to created_at when paid_at missing).
|
||||||
|
if ($paid_at !== '' || in_array($status, $paid_like, true)) {
|
||||||
|
$push(
|
||||||
|
$timeline,
|
||||||
|
'paid',
|
||||||
|
__('完成支付', 'aksrt'),
|
||||||
|
$paid_at !== '' ? $paid_at : $created_at
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Shipped / delivered — only when logistics exists or status reached it.
|
||||||
|
$has_shipping = $shipping_records !== [];
|
||||||
|
if (
|
||||||
|
$delivered_at !== ''
|
||||||
|
|| $has_shipping
|
||||||
|
|| in_array($status, $ship_like, true)
|
||||||
|
) {
|
||||||
|
// Virtual / content flows often jump paid → completed without logistics;
|
||||||
|
// only show the ship step when we have evidence of shipping.
|
||||||
|
if ($delivered_at !== '' || $has_shipping || $status === Order::STATUS_DELIVERED) {
|
||||||
|
$push(
|
||||||
|
$timeline,
|
||||||
|
'delivered',
|
||||||
|
__('订单发货', 'aksrt'),
|
||||||
|
$delivered_at !== '' ? $delivered_at : ($paid_at !== '' ? $paid_at : $created_at)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) Completed / pending review.
|
||||||
|
if ($completed_at !== '' || in_array($status, $done_like, true)) {
|
||||||
|
$done_label = $status === Order::STATUS_PENDING_REVIEW
|
||||||
|
? __('待评价', 'aksrt')
|
||||||
|
: __('交易完成', 'aksrt');
|
||||||
|
$push(
|
||||||
|
$timeline,
|
||||||
|
'completed',
|
||||||
|
$done_label,
|
||||||
|
$completed_at !== ''
|
||||||
|
? $completed_at
|
||||||
|
: ($delivered_at !== '' ? $delivered_at : ($paid_at !== '' ? $paid_at : $created_at))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5) Terminal cancel / expire / refund.
|
||||||
|
if ($status === Order::STATUS_CANCELLED || $cancelled_at !== '') {
|
||||||
|
$push(
|
||||||
|
$timeline,
|
||||||
|
'cancelled',
|
||||||
|
__('订单取消', 'aksrt'),
|
||||||
|
$cancelled_at !== '' ? $cancelled_at : $created_at
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ($status === Order::STATUS_EXPIRED) {
|
||||||
|
$push(
|
||||||
|
$timeline,
|
||||||
|
'expired',
|
||||||
|
__('订单过期', 'aksrt'),
|
||||||
|
$cancelled_at !== '' ? $cancelled_at : $created_at
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ($status === Order::STATUS_REFUNDING) {
|
||||||
|
$push(
|
||||||
|
$timeline,
|
||||||
|
'refunding',
|
||||||
|
__('退款处理中', 'aksrt'),
|
||||||
|
$refunded_at !== '' ? $refunded_at : ($paid_at !== '' ? $paid_at : $created_at)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ($status === Order::STATUS_REFUNDED || $refunded_at !== '') {
|
||||||
|
$push(
|
||||||
|
$timeline,
|
||||||
|
'refunded',
|
||||||
|
__('完成退款', 'aksrt'),
|
||||||
|
$refunded_at !== '' ? $refunded_at : ($paid_at !== '' ? $paid_at : $created_at)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extra logistics / after-sale events (detail lines).
|
||||||
|
foreach ($shipping_records as $record) {
|
||||||
|
$tracking = trim((string) ($record['tracking_no'] ?? ''));
|
||||||
|
$carrier = trim((string) ($record['carrier_label'] ?? $record['carrier'] ?? ''));
|
||||||
|
$status_l = trim((string) ($record['status_label'] ?? $record['status'] ?? ''));
|
||||||
|
$parts = array_filter([$carrier, $tracking, $status_l], static fn($v) => $v !== '');
|
||||||
|
$label = $parts !== []
|
||||||
|
? sprintf(__('物流更新:%s', 'aksrt'), implode(' / ', $parts))
|
||||||
|
: __('物流更新', 'aksrt');
|
||||||
|
$push(
|
||||||
|
$timeline,
|
||||||
|
'shipping',
|
||||||
|
$label,
|
||||||
|
$this->timeline_time((string) ($record['created_at'] ?? $record['updated_at'] ?? ''))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
foreach ($after_sale_records as $record) {
|
foreach ($after_sale_records as $record) {
|
||||||
$timeline[] = [
|
$reason = trim((string) ($record['reason'] ?? ''));
|
||||||
'time' => (string) ($record['created_at'] ?? ''),
|
$type_l = trim((string) ($record['type_label'] ?? $record['type'] ?? ''));
|
||||||
'label' => sprintf(__('售后申请:%s', 'aksrt'), $record['reason'] ?? ''),
|
$as_status = trim((string) ($record['status_label'] ?? $record['status'] ?? ''));
|
||||||
];
|
$parts = array_filter([$type_l, $as_status, $reason], static fn($v) => $v !== '');
|
||||||
|
$label = $parts !== []
|
||||||
|
? sprintf(__('售后:%s', 'aksrt'), implode(' — ', $parts))
|
||||||
|
: __('售后申请', 'aksrt');
|
||||||
|
$push(
|
||||||
|
$timeline,
|
||||||
|
'after_sale',
|
||||||
|
$label,
|
||||||
|
$this->timeline_time((string) ($record['created_at'] ?? $record['updated_at'] ?? ''))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
usort($timeline, static function ($a, $b) {
|
usort($timeline, static function ($a, $b) {
|
||||||
return strcmp($a['time'] ?? '', $b['time'] ?? '');
|
$ta = (string) ($a['time'] ?? '');
|
||||||
|
$tb = (string) ($b['time'] ?? '');
|
||||||
|
if ($ta === $tb) {
|
||||||
|
// Keep canonical pipeline order when times collide / missing.
|
||||||
|
$rank = [
|
||||||
|
'created' => 10,
|
||||||
|
'paid' => 20,
|
||||||
|
'delivered' => 30,
|
||||||
|
'shipping' => 35,
|
||||||
|
'completed' => 40,
|
||||||
|
'refunding' => 50,
|
||||||
|
'refunded' => 60,
|
||||||
|
'cancelled' => 70,
|
||||||
|
'expired' => 80,
|
||||||
|
'after_sale'=> 90,
|
||||||
|
];
|
||||||
|
$ra = $rank[(string) ($a['key'] ?? '')] ?? 99;
|
||||||
|
$rb = $rank[(string) ($b['key'] ?? '')] ?? 99;
|
||||||
|
return $ra <=> $rb;
|
||||||
|
}
|
||||||
|
if ($ta === '') {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if ($tb === '') {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return strcmp($ta, $tb);
|
||||||
});
|
});
|
||||||
|
|
||||||
return $timeline;
|
return $timeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a mysql datetime for timeline display (empty when invalid/zero).
|
||||||
|
*/
|
||||||
|
private function timeline_time(string $value): string {
|
||||||
|
$value = trim($value);
|
||||||
|
if ($value === '' || str_starts_with($value, '0000-00-00')) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
private function get_order_countdown(string $expires_at, string $status): int {
|
private function get_order_countdown(string $expires_at, string $status): int {
|
||||||
if ($expires_at === '' || !in_array($status, [Order::STATUS_PENDING, Order::STATUS_EXPIRED, Order::STATUS_CREATED], true)) {
|
if ($expires_at === '' || !in_array($status, [Order::STATUS_PENDING, Order::STATUS_EXPIRED, Order::STATUS_CREATED], true)) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
+39
-22
@@ -556,44 +556,61 @@ trait Part1 {
|
|||||||
return $view;
|
return $view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shop-side timeline; keep behaviour aligned with UserOrders.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $order
|
||||||
|
* @param array<int, array> $shipping_records
|
||||||
|
* @param array<int, array> $after_sale_records
|
||||||
|
* @return array<int, array{time:string,label:string,key?:string}>
|
||||||
|
*/
|
||||||
private function build_order_timeline(array $order, array $shipping_records, array $after_sale_records): array {
|
private function build_order_timeline(array $order, array $shipping_records, array $after_sale_records): array {
|
||||||
$timeline = [];
|
// Prefer the unified order-center builder when available.
|
||||||
$date_fields = [
|
if (class_exists('\AKSRT\Payment\UserOrders')) {
|
||||||
'created_at' => __('订单创建', 'aksrt'),
|
$view = \AKSRT\Payment\UserOrders::get_instance()->decorate_order_view($order, true);
|
||||||
'paid_at' => __('完成支付', 'aksrt'),
|
if (is_array($view['timeline'] ?? null)) {
|
||||||
'delivered_at' => __('订单发货', 'aksrt'),
|
return $view['timeline'];
|
||||||
'completed_at' => __('交易完成', 'aksrt'),
|
|
||||||
'cancelled_at' => __('订单取消', 'aksrt'),
|
|
||||||
'refunded_at' => __('完成退款', 'aksrt'),
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($date_fields as $key => $label) {
|
|
||||||
if (!empty($order[$key])) {
|
|
||||||
$timeline[] = [
|
|
||||||
'time' => (string) $order[$key],
|
|
||||||
'label' => $label,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$timeline = [];
|
||||||
|
$created = (string) ($order['created_at'] ?? '');
|
||||||
|
if ($created !== '') {
|
||||||
|
$timeline[] = ['key' => 'created', 'time' => $created, 'label' => __('订单创建', 'aksrt')];
|
||||||
|
}
|
||||||
|
$status = (string) ($order['status'] ?? '');
|
||||||
|
$paid_like = [
|
||||||
|
\AKSRT\Payment\Order::STATUS_PAID,
|
||||||
|
\AKSRT\Payment\Order::STATUS_DELIVERED,
|
||||||
|
\AKSRT\Payment\Order::STATUS_COMPLETED,
|
||||||
|
\AKSRT\Payment\Order::STATUS_PENDING_REVIEW,
|
||||||
|
\AKSRT\Payment\Order::STATUS_REFUNDED,
|
||||||
|
\AKSRT\Payment\Order::STATUS_REFUNDING,
|
||||||
|
];
|
||||||
|
if (!empty($order['paid_at']) || in_array($status, $paid_like, true)) {
|
||||||
|
$timeline[] = [
|
||||||
|
'key' => 'paid',
|
||||||
|
'time' => (string) ($order['paid_at'] ?? $created),
|
||||||
|
'label' => __('完成支付', 'aksrt'),
|
||||||
|
];
|
||||||
|
}
|
||||||
foreach ($shipping_records as $record) {
|
foreach ($shipping_records as $record) {
|
||||||
$timeline[] = [
|
$timeline[] = [
|
||||||
'time' => (string) ($record['created_at'] ?? ''),
|
'key' => 'shipping',
|
||||||
|
'time' => (string) ($record['created_at'] ?? ''),
|
||||||
'label' => sprintf(__('物流更新:%s', 'aksrt'), $record['tracking_no'] ?? ''),
|
'label' => sprintf(__('物流更新:%s', 'aksrt'), $record['tracking_no'] ?? ''),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($after_sale_records as $record) {
|
foreach ($after_sale_records as $record) {
|
||||||
$timeline[] = [
|
$timeline[] = [
|
||||||
'time' => (string) ($record['created_at'] ?? ''),
|
'key' => 'after_sale',
|
||||||
|
'time' => (string) ($record['created_at'] ?? ''),
|
||||||
'label' => sprintf(__('售后申请:%s', 'aksrt'), $record['reason'] ?? ''),
|
'label' => sprintf(__('售后申请:%s', 'aksrt'), $record['reason'] ?? ''),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
usort($timeline, static function ($a, $b) {
|
usort($timeline, static function ($a, $b) {
|
||||||
return strcmp($a['time'] ?? '', $b['time'] ?? '');
|
return strcmp((string) ($a['time'] ?? ''), (string) ($b['time'] ?? ''));
|
||||||
});
|
});
|
||||||
|
|
||||||
return $timeline;
|
return $timeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user