feat(energy): integrate self-operated station customers and receipts
ci/woodpecker/push/woodpecker Pipeline was canceled
ci/woodpecker/push/woodpecker Pipeline was canceled
Co-authored-by: HiFox Agent <agents-noreply@hifox.com>
This commit is contained in:
+45
-31
@@ -194,18 +194,19 @@ export const StationDailyDetailView: React.FC<{
|
||||
return idx > 0 ? sorted[idx - 1].quantityKg : null;
|
||||
}, [asOfVolume, volumeRows]);
|
||||
|
||||
const customerMonthRows = liveBoard?.selected?.customerMonths;
|
||||
const allCustCells = useMemo(
|
||||
() => {
|
||||
const byCustomer = new Map<string, Record<string, number>>();
|
||||
for (const row of liveBoard?.selected?.customerMonths ?? []) {
|
||||
for (const row of customerMonthRows ?? []) {
|
||||
const months = byCustomer.get(row.customerName) ?? {};
|
||||
const monthKey = String(row.month).slice(0, 7);
|
||||
months[monthKey] = row.kg;
|
||||
months[monthKey] = (months[monthKey] ?? 0) + row.kg;
|
||||
byCustomer.set(row.customerName, months);
|
||||
}
|
||||
return [...byCustomer].map(([customerName, months]) => ({ stationId, customerName, months }));
|
||||
},
|
||||
[liveBoard, stationId],
|
||||
[customerMonthRows, stationId],
|
||||
);
|
||||
const customerOptions = useMemo(
|
||||
() => allCustCells.map((c) => c.customerName),
|
||||
@@ -220,14 +221,14 @@ export const StationDailyDetailView: React.FC<{
|
||||
|
||||
const allFeeCells = useMemo(() => {
|
||||
const byCustomer = new Map<string, Record<string, number>>();
|
||||
for (const row of liveBoard?.selected?.customerMonths ?? []) {
|
||||
for (const row of customerMonthRows ?? []) {
|
||||
const months = byCustomer.get(row.customerName) ?? {};
|
||||
const monthKey = String(row.month).slice(0, 7);
|
||||
months[monthKey] = row.fee;
|
||||
months[monthKey] = (months[monthKey] ?? 0) + row.fee;
|
||||
byCustomer.set(row.customerName, months);
|
||||
}
|
||||
return [...byCustomer].map(([customerName, months]) => ({ stationId, customerName, months }));
|
||||
}, [liveBoard, stationId]);
|
||||
}, [customerMonthRows, stationId]);
|
||||
const feeCells = useMemo(() => {
|
||||
const allowed = new Set(custCells.map((row) => row.customerName));
|
||||
return allFeeCells.filter((row) => allowed.has(row.customerName));
|
||||
@@ -254,17 +255,15 @@ export const StationDailyDetailView: React.FC<{
|
||||
|
||||
const cashLines = useMemo(
|
||||
() =>
|
||||
cashDays.flatMap((d) =>
|
||||
d.lines.map((l) => ({
|
||||
id: l.id,
|
||||
bizDate: padYmd(d.bizDate),
|
||||
customerName: l.customerName,
|
||||
payMethod: l.payMethod,
|
||||
amount: l.amount,
|
||||
})),
|
||||
),
|
||||
[cashDays],
|
||||
(liveBoard?.selected?.externalReceipts?.rows ?? []).map((row) => ({
|
||||
...row, bizDate: row.date,
|
||||
sourceLabel: row.source === 'spot_daily_auto' ? '现结自动汇总'
|
||||
: row.source === 'external_recharge_manual' ? '手工充值' : `未知来源(${row.source})`,
|
||||
payLabel: row.payMethod === 'corporate' ? '对公转账' : row.payMethod === 'wechat' ? '微信' : row.payMethod || '未注明',
|
||||
})),
|
||||
[liveBoard],
|
||||
);
|
||||
const receiptTotal = cashLines.reduce((sum, row) => sum + row.amount, 0);
|
||||
const cashVisible = expandCash ? cashLines : cashLines.slice(0, ROW_LIMIT);
|
||||
|
||||
const cashTotal = cashDays.reduce((s, d) => s + d.totalAmount, 0);
|
||||
@@ -292,6 +291,9 @@ export const StationDailyDetailView: React.FC<{
|
||||
setExporting(true);
|
||||
setExportError(null);
|
||||
try {
|
||||
if (!liveBoard?.selected?.customerMonths || !liveBoard?.selected?.externalReceipts) {
|
||||
throw new Error('客户数据未完整返回,暂不导出,请刷新后重试');
|
||||
}
|
||||
const result = await fetchAllH2BiDrillRecords({
|
||||
year: Number(rangeStart.slice(0, 4)), startDate: rangeStart, endDate: rangeEnd,
|
||||
vehicleScope: 'all', verifyScope: 'all', stationId,
|
||||
@@ -340,11 +342,15 @@ export const StationDailyDetailView: React.FC<{
|
||||
b.balanceYuan,
|
||||
b.remark || '',
|
||||
]),
|
||||
['小计', balanceSubtotal.recharge, balanceSubtotal.prepaid, balanceSubtotal.spot, balanceSubtotal.balance, ''],
|
||||
['未接入完整账户余额,不以零代替缺失数据'],
|
||||
[],
|
||||
['氢费充值/现结进账明细'],
|
||||
['充值日期', '客户', '付款方式', '金额(元)'],
|
||||
...cashLines.map((l) => [l.bizDate, l.customerName, SPOT_PAY_METHOD_LABEL[l.payMethod], l.amount]),
|
||||
['外部客户进账(当前租户全部外部客户,不按站点归属;不计入单站收益)'],
|
||||
['日期', '客户', '付款方式', '金额(元)', '来源', '源记录数', '刷新时间', '记录ID'],
|
||||
...cashLines.map((l) => [l.bizDate, l.customerName, l.payLabel, l.amount, l.sourceLabel, l.sourceRecordCount, l.updatedAt ?? '', l.id]),
|
||||
[],
|
||||
['自营站全部车辆客户加氢月度(不含手工充值)'],
|
||||
['月份', '客户', '加氢量(Kg)', '加氢金额(元)', '记录数'],
|
||||
...(liveBoard?.selected?.customerMonths ?? []).map((r) => [r.month, r.customerName, r.kg, r.fee, r.recordCount]),
|
||||
[],
|
||||
['车辆加氢明细(查询区间全部真实账本记录)', result.records.length],
|
||||
['日期', '车牌', '客户', '归属', '加氢量(Kg)', '单价', '金额(元)'],
|
||||
@@ -491,13 +497,14 @@ export const StationDailyDetailView: React.FC<{
|
||||
<div className="sd-hero-kpi__sub">{asOf.slice(0, 7)} · 仅计入查询区间</div>
|
||||
</div>
|
||||
<div className="sd-hero-kpi">
|
||||
<div className="sd-hero-kpi__label">查询区间现结</div>
|
||||
<div className="sd-hero-kpi__label">单站现结流水</div>
|
||||
<div className="sd-hero-kpi__value">
|
||||
{money(cashTotal)}
|
||||
<span className="sd-unit">元</span>
|
||||
</div>
|
||||
<div className="sd-hero-kpi__sub">
|
||||
{cashDays.length ? `${cashDays.length} 天有进账 · ${dateRangeLabel(startDate, end)}` : `0 天有进账 · ${dateRangeLabel(startDate, end)}`}
|
||||
<br />不含客户级充值/进账
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -678,11 +685,12 @@ export const StationDailyDetailView: React.FC<{
|
||||
onChange={setSelectedCustomers}
|
||||
/>
|
||||
</div>
|
||||
<p className="sd-panel__meta">自营站全部车辆与客户,包含羚牛及外部车辆;未关联客户保留统计,不含手工充值。</p>
|
||||
<MobileCustomerMonthList key={stationId} months={customerMonthKeys}
|
||||
volumeCustomers={allCustCells} feeCustomers={allFeeCells}
|
||||
month={mobileMonthKey} onMonthChange={setMobileMonthKey}
|
||||
metric={customerMonthlyMetric} onMetricChange={setCustomerMonthlyMetric}
|
||||
loading={liveLoading} error={liveError} />
|
||||
loading={liveLoading} error={liveError || (!customerMonthRows ? '客户数据暂不可用' : null)} />
|
||||
<div className="sd-table-scroll sd-table-scroll--matrix sd-desktop-matrix-table">
|
||||
<table className={`sd-bi-table sd-bi-table--matrix ${customerMonthlyMetric === 'fee' ? 'is-amount' : ''}`}>
|
||||
<thead>
|
||||
@@ -708,7 +716,7 @@ export const StationDailyDetailView: React.FC<{
|
||||
{(customerMonthlyMetric === 'volume' ? custCells : feeCells).length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={1 + customerMonthKeys.length} className="ehb-empty-cell">
|
||||
无匹配客户
|
||||
{!customerMonthRows ? '客户数据暂不可用' : '无匹配客户'}
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
@@ -745,6 +753,7 @@ export const StationDailyDetailView: React.FC<{
|
||||
<div className="sd-dual sd-dual--cash sd-dual--ledger">
|
||||
<section className={`sd-panel sd-mobile-detail-panel ${mobileDetailTab === 'balance' ? 'is-active' : ''}`} data-mobile-fullscreen-list>
|
||||
<h2 className="sd-panel__title">客户氢费收支汇总</h2>
|
||||
<p className="sd-panel__meta">完整账户余额未接入:新进账表不能单独推算余额或经营利润。</p>
|
||||
<div className="sd-mobile-record-list sd-mobile-balance-list">
|
||||
{balVisible.map((customer) => (
|
||||
<article key={customer.customerName} className="sd-mobile-record">
|
||||
@@ -813,16 +822,19 @@ export const StationDailyDetailView: React.FC<{
|
||||
<MoreToggle expanded={expandBal} total={balanceRows.length} onToggle={() => setExpandBal((v) => !v)} />
|
||||
</section>
|
||||
|
||||
<section className={`sd-panel sd-mobile-detail-panel ${mobileDetailTab === 'cash' ? 'is-active' : ''}`} data-mobile-fullscreen-list>
|
||||
<div className="sd-panel__head-row">
|
||||
<h2 className="sd-panel__title">氢费充值/现结进账明细</h2>
|
||||
<span className="sd-panel__meta">合计 ¥{money(cashTotal)} · {cashLines.length} 笔</span>
|
||||
<section className={`sd-panel sd-mobile-detail-panel sd-external-receipts ${mobileDetailTab === 'cash' ? 'is-active' : ''}`} data-mobile-fullscreen-list>
|
||||
<div className="sd-panel__head-row sd-external-receipt-head">
|
||||
<h2 className="sd-panel__title">外部客户充值/现结进账</h2>
|
||||
<span className="sd-panel__meta">{liveBoard?.selected?.externalReceipts ? `合计 ¥${money(receiptTotal)} · ${cashLines.length} 条` : '金额暂不可用'}</span>
|
||||
</div>
|
||||
<p className="sd-panel__meta">当前租户全部外部客户,不按站点归属。现结自动汇总已包含在加氢业务中,不重复计入单站现结或收益;充值也不等于利润。</p>
|
||||
{!cashLines.length ? <p className="sd-panel__meta">{liveBoard?.selected?.externalReceipts ? '查询区间暂无客户进账记录' : '客户进账数据暂不可用:接口未返回新数据,请刷新或检查服务版本。'}</p> : null}
|
||||
<div className="sd-mobile-record-list sd-mobile-cash-list">
|
||||
{cashVisible.map((line) => (
|
||||
<article key={line.id} className="sd-mobile-record">
|
||||
<div className="sd-mobile-record__lead"><strong>{line.customerName}</strong><span>{line.bizDate}</span></div>
|
||||
<div className="sd-mobile-record__value"><strong className={businessValueClass(line.amount)}>¥{money(line.amount)}</strong><span className="sd-mobile-pay-tag">{SPOT_PAY_METHOD_LABEL[line.payMethod]}</span></div>
|
||||
<div className="sd-mobile-record__value"><strong className={businessValueClass(line.amount)}>¥{money(line.amount)}</strong><span className="sd-mobile-pay-tag">{line.payLabel}</span></div>
|
||||
<div className="sd-mobile-record__meta">{line.sourceLabel} · {line.sourceRecordCount} 条源记录{line.updatedAt ? ` · 更新 ${line.updatedAt}` : ''}</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
@@ -833,14 +845,15 @@ export const StationDailyDetailView: React.FC<{
|
||||
<th>充值日期</th>
|
||||
<th>客户</th>
|
||||
<th>付款方式</th>
|
||||
<th>来源 / 源记录数</th>
|
||||
<th className="is-num">金额(元)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{cashLines.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={4} className="ehb-empty-cell">
|
||||
本窗暂无进账明细
|
||||
<td colSpan={5} className="ehb-empty-cell">
|
||||
{liveBoard?.selected?.externalReceipts ? '本窗暂无进账明细' : '客户进账数据暂不可用'}
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
@@ -848,7 +861,8 @@ export const StationDailyDetailView: React.FC<{
|
||||
<tr key={l.id}>
|
||||
<td className="is-mono">{l.bizDate}</td>
|
||||
<td title={l.customerName}>{l.customerName}</td>
|
||||
<td>{SPOT_PAY_METHOD_LABEL[l.payMethod]}</td>
|
||||
<td>{l.payLabel}</td>
|
||||
<td>{l.sourceLabel} / {l.sourceRecordCount}</td>
|
||||
<td className={`is-num ${businessValueClass(l.amount)}`}>{money(l.amount)}</td>
|
||||
</tr>
|
||||
))
|
||||
|
||||
@@ -288,6 +288,11 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sd-embedded .sd-detail.sd-detail--mobile-pilot .sd-mobile-detail-panel > .sd-panel__head-row.sd-external-receipt-head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.sd-panel__head-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
@@ -2803,6 +2808,16 @@
|
||||
.sd-embedded .sd-detail.sd-detail--mobile-pilot .sd-dual--ledger .sd-table-scroll {
|
||||
display: block;
|
||||
}
|
||||
.sd-embedded .sd-detail.sd-detail--mobile-pilot .sd-mobile-detail-hub:not([data-mobile-fullscreen-active="true"]) .sd-external-receipts > .sd-table-scroll {
|
||||
display: none;
|
||||
}
|
||||
.sd-embedded .sd-detail.sd-detail--mobile-pilot .sd-mobile-detail-hub:not([data-mobile-fullscreen-active="true"]) .sd-external-receipts > .sd-mobile-record-list {
|
||||
display: grid !important;
|
||||
gap: 8px;
|
||||
}
|
||||
.sd-embedded .sd-detail.sd-detail--mobile-pilot .sd-external-receipts > .sd-more-btn {
|
||||
display: inline-flex;
|
||||
}
|
||||
.sd-embedded .sd-detail.sd-detail--mobile-pilot .sd-mobile-detail-hub[data-mobile-fullscreen-active="true"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user