Files
lotteryFront/src/features/orders/group-ticket-items.ts
kang e5622c58cb
Some checks failed
lotteryfront CI / build (push) Has been cancelled
feat: 精简玩家端页面结构并支持局域网开发热更新
- 合并钱包、订单、开奖、通知等独立 screen 到页面层,减少重复壳组件
- 优化订单列表/详情、钱包划转与待对账展示、开奖核对与结果详情交互
- 改进入口 gate、移动端视口与下拉刷新在窄屏下的表现
- dev 绑定 0.0.0.0 并默认放行 192.168/10 网段,修复局域网 HMR WebSocket
2026-06-26 14:10:47 +08:00

138 lines
4.2 KiB
TypeScript

import type { TicketItemListRow } from "@/types/api/ticket-items";
export type TicketItemGroup = {
key: string;
order_no: string | null;
order_status: string | null;
draw_no: string | null;
placed_at: string | null;
currency_code: string | null;
status: string;
items: TicketItemListRow[];
total_bet_amount: number;
actual_deduct_amount: number;
win_amount: number;
jackpot_win_amount: number;
};
/** 分组键:有 order_no 则按订单;否则 draw_no + placed_at + currency + status 兜底 */
export function getTicketItemGroupKey(row: TicketItemListRow): string {
const orderNo = (row.order_no ?? "").trim();
if (orderNo) {
return `order:${orderNo}`;
}
const drawNo = row.draw_no ?? "";
const placedAt = row.placed_at ?? "";
const currency = row.currency_code ?? "";
const status = row.status ?? "";
return `fallback:${drawNo}|${placedAt}|${currency}|${status}`;
}
function resolveGroupStatus(items: TicketItemListRow[]): string {
const orderStatus = items.map((row) => row.order_status).find((s) => s != null && s !== "");
if (orderStatus === "partial_failed" || orderStatus === "partial_pending_confirm") {
return orderStatus;
}
const hasStatus = (status: string): boolean => items.some((row) => row.status === status);
const hasAny = (statuses: string[]): boolean => statuses.some((status) => hasStatus(status));
const allMatch = (statuses: string[]): boolean =>
items.length > 0 && items.every((row) => statuses.includes(row.status));
const hasWinningItem = items.some(
(row) => row.status === "settled_win" && row.win_amount + row.jackpot_win_amount > 0,
);
if (hasStatus("failed") && hasAny(["pending_draw", "placed", "pending_confirm"])) {
return "partial_failed";
}
if (hasAny(["pending_confirm", "partial_pending_confirm"])) {
return hasAny(["failed", "pending_draw", "placed", "pending_payout", "settled_win", "settled_lose"])
? "partial_pending_confirm"
: "pending_confirm";
}
if (hasAny(["pending_draw", "placed"])) {
return "pending_draw";
}
if (hasStatus("pending_payout")) {
return "pending_payout";
}
if (hasWinningItem) {
return "settled_win";
}
if (allMatch(["failed"])) {
return "failed";
}
if (allMatch(["refunded"])) {
return "refunded";
}
if (allMatch(["settled_lose", "settled_win"])) {
return "settled_lose";
}
return items[0]?.status ?? "unknown";
}
export function sortTicketGroupItems(items: TicketItemListRow[]): TicketItemListRow[] {
return [...items].sort((a, b) => {
const byPlay = a.play_code.localeCompare(b.play_code);
if (byPlay !== 0) return byPlay;
return (a.original_number ?? "").localeCompare(b.original_number ?? "");
});
}
/** 保持 API 返回顺序下,按分组键首次出现顺序输出合并组 */
export function groupTicketItems(items: TicketItemListRow[]): TicketItemGroup[] {
const map = new Map<string, TicketItemGroup>();
const order: string[] = [];
for (const row of items) {
const key = getTicketItemGroupKey(row);
let group = map.get(key);
if (!group) {
const orderNo = (row.order_no ?? "").trim();
group = {
key,
order_no: orderNo || null,
order_status: row.order_status ?? null,
draw_no: row.draw_no ?? null,
placed_at: row.placed_at ?? null,
currency_code: row.currency_code ?? null,
status: row.status,
items: [],
total_bet_amount: 0,
actual_deduct_amount: 0,
win_amount: 0,
jackpot_win_amount: 0,
};
map.set(key, group);
order.push(key);
}
group.items.push(row);
group.total_bet_amount += row.total_bet_amount;
group.actual_deduct_amount += row.actual_deduct_amount;
group.win_amount += row.win_amount;
group.jackpot_win_amount += row.jackpot_win_amount;
}
return order.map((key) => {
const group = map.get(key)!;
const sortedItems = sortTicketGroupItems(group.items);
return {
...group,
items: sortedItems,
status: resolveGroupStatus(sortedItems),
order_status: sortedItems[0]?.order_status ?? group.order_status,
};
});
}
export function ticketDetailHref(ticketNo: string): string {
return `/orders/${encodeURIComponent(ticketNo)}`;
}