fix: 删除月度期末余额汇总卡片
删除账户余额总览中的月度汇总卡片、切换、接口及关联代码。
This commit is contained in:
parent
dde4531ff4
commit
e5faacd2bf
@ -25,21 +25,6 @@ class StoreReceivingBalanceOverview extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
public function monthlyClosing(Request $request, StoreReceivingBalanceOverviewService $service)
|
||||
{
|
||||
$this->authorizeStoreReceivingBills();
|
||||
|
||||
try {
|
||||
return response()->json($service->monthlyClosing(...$this->requestedPeriod($request)));
|
||||
} catch (Throwable $exception) {
|
||||
report($exception);
|
||||
|
||||
return response()->json([
|
||||
'message' => '月度期末余额汇总暂时无法读取,请稍后重试。',
|
||||
], 503);
|
||||
}
|
||||
}
|
||||
|
||||
public function incomeDetail(Request $request, StoreReceivingBalanceOverviewService $service)
|
||||
{
|
||||
$this->authorizeStoreReceivingBills();
|
||||
|
||||
@ -151,98 +151,6 @@ class StoreReceivingBalanceOverviewService
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 选定月份各平台期末余额。只读取当前发生时间对应的一个月。
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function monthlyClosing(string $periodStart, ?string $periodEnd = null): array
|
||||
{
|
||||
[$periodStart, $periodEnd, $periodMonth] = $this->periodForRange($periodStart, $periodEnd);
|
||||
$periodOverview = $this->overview($periodStart, $periodEnd);
|
||||
$rows = [];
|
||||
$availableRows = 0;
|
||||
|
||||
foreach (self::PLATFORMS as $definition) {
|
||||
$platform = $this->platformFromOverview($periodOverview, $definition['slug']);
|
||||
$balance = $platform !== null
|
||||
&& $platform['available']
|
||||
&& is_numeric($platform['balance'] ?? null)
|
||||
? round((float) $platform['balance'], 2)
|
||||
: null;
|
||||
$available = $balance !== null;
|
||||
if ($available) {
|
||||
$availableRows++;
|
||||
}
|
||||
$rows[] = $this->monthlyClosingRow(
|
||||
$definition['slug'],
|
||||
$definition['name'],
|
||||
[$periodMonth => $balance],
|
||||
$available,
|
||||
);
|
||||
}
|
||||
|
||||
$totalBalance = $periodOverview['summary']['complete']
|
||||
&& is_numeric($periodOverview['summary']['balance'] ?? null)
|
||||
? round((float) $periodOverview['summary']['balance'], 2)
|
||||
: null;
|
||||
|
||||
return [
|
||||
'month' => $periodMonth,
|
||||
'period' => [
|
||||
'start' => $periodStart,
|
||||
'end' => $periodEnd,
|
||||
],
|
||||
'months' => [[
|
||||
'key' => $periodMonth,
|
||||
'label' => (int) CarbonImmutable::parse($periodStart, 'Asia/Shanghai')->format('n') . '月',
|
||||
]],
|
||||
'summary' => [
|
||||
'platform_count' => count(self::PLATFORMS),
|
||||
'available_platform_count' => $availableRows,
|
||||
'complete' => $availableRows === count(self::PLATFORMS) && $totalBalance !== null,
|
||||
],
|
||||
'total' => $this->monthlyClosingRow(
|
||||
'total',
|
||||
'合计',
|
||||
[$periodMonth => $totalBalance],
|
||||
$totalBalance !== null,
|
||||
),
|
||||
'platforms' => $rows,
|
||||
];
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $overview */
|
||||
private function platformFromOverview(array $overview, string $slug): ?array
|
||||
{
|
||||
foreach ($overview['platforms'] ?? [] as $platform) {
|
||||
if (($platform['slug'] ?? null) === $slug) {
|
||||
return $platform;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,float|null> $balances
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private function monthlyClosingRow(
|
||||
string $slug,
|
||||
string $name,
|
||||
array $balances,
|
||||
bool $available,
|
||||
): array
|
||||
{
|
||||
return [
|
||||
'slug' => $slug,
|
||||
'name' => $name,
|
||||
'available' => $available,
|
||||
'balances' => $balances,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 收入合计的平台、店铺、账单项目明细。
|
||||
*
|
||||
|
||||
@ -125,126 +125,6 @@
|
||||
</template>
|
||||
</section>
|
||||
|
||||
<section class="monthly-closing-card" aria-labelledby="monthly-closing-title" :aria-busy="monthlyClosingLoading ? 'true' : 'false'">
|
||||
<header class="monthly-closing-head">
|
||||
<div>
|
||||
<div class="monthly-closing-title-row">
|
||||
<h3 id="monthly-closing-title">月度期末余额汇总</h3>
|
||||
<span class="monthly-closing-badge">新增</span>
|
||||
</div>
|
||||
<p>
|
||||
{{ monthlyClosingMonthLabel }}{{ monthlyClosingView === 'store' ? '店铺' : '各平台' }}期末余额
|
||||
· 金额单位:万元
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="!monthlyClosingLoading && !monthlyClosingError" class="monthly-closing-head-actions">
|
||||
<span class="monthly-closing-count">{{ monthlyClosingCountText }}</span>
|
||||
<div class="monthly-closing-view-switch" role="tablist" aria-label="月度期末余额汇总方式">
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
:aria-selected="monthlyClosingView === 'platform' ? 'true' : 'false'"
|
||||
:class="{ 'is-active': monthlyClosingView === 'platform' }"
|
||||
@click="selectMonthlyClosingView('platform')"
|
||||
>平台账户</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
:aria-selected="monthlyClosingView === 'store' ? 'true' : 'false'"
|
||||
:class="{ 'is-active': monthlyClosingView === 'store' }"
|
||||
@click="selectMonthlyClosingView('store')"
|
||||
>店铺汇总</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div v-if="monthlyClosingLoading" class="monthly-closing-loading" role="status">
|
||||
<span v-for="index in 4" :key="index"></span>
|
||||
<p>正在汇总 {{ monthlyClosingMonthLabel }}期末余额…</p>
|
||||
</div>
|
||||
<div v-else-if="monthlyClosingError" class="monthly-closing-error" role="alert">
|
||||
<span>{{ monthlyClosingError }}</span>
|
||||
<button type="button" @click="fetchMonthlyClosing">重新加载</button>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="monthlyClosingView === 'store' && monthlyClosingStoreLoading"
|
||||
class="monthly-closing-loading"
|
||||
role="status"
|
||||
>
|
||||
<span v-for="index in 4" :key="index"></span>
|
||||
<p>正在汇总 {{ monthlyClosingMonthLabel }}店铺期末余额…</p>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="monthlyClosingView === 'store' && monthlyClosingStoreError"
|
||||
class="monthly-closing-error"
|
||||
role="alert"
|
||||
>
|
||||
<span>{{ monthlyClosingStoreError }}</span>
|
||||
<button type="button" @click="fetchMonthlyClosingStores">重新加载</button>
|
||||
</div>
|
||||
<div v-else-if="monthlyClosingView === 'platform'" class="monthly-closing-scroll">
|
||||
<table class="monthly-closing-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">平台账户</th>
|
||||
<th v-for="month in monthlyClosing.months" :key="month.key" scope="col" class="is-number monthly-closing-month-head">
|
||||
{{ month.label }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="row in monthlyClosingRows"
|
||||
:key="row.slug"
|
||||
:class="{ 'is-total': row.slug === 'total', 'is-unavailable': !row.available }"
|
||||
:data-platform-slug="row.slug"
|
||||
>
|
||||
<th scope="row">{{ row.name }}</th>
|
||||
<td v-for="month in monthlyClosing.months" :key="month.key" class="is-number">
|
||||
{{ monthlyBalanceWan(row, month.key) }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="monthly-closing-scroll is-store">
|
||||
<table class="monthly-closing-table monthly-closing-store-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">平台账户</th>
|
||||
<th scope="col">店铺名称</th>
|
||||
<th v-for="month in monthlyClosing.months" :key="month.key" scope="col" class="is-number monthly-closing-month-head">
|
||||
{{ month.label }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="row in monthlyStoreRows"
|
||||
:key="row.key"
|
||||
:class="{ 'is-total': row.isTotal, 'is-unavailable': !row.available }"
|
||||
>
|
||||
<th scope="row">{{ row.platform }}</th>
|
||||
<td class="monthly-store-name">{{ row.store_name }}</td>
|
||||
<td v-for="month in monthlyClosing.months" :key="month.key" class="is-number">
|
||||
{{ monthlyBalanceWan(row, month.key) }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
<footer class="monthly-closing-foot">
|
||||
<span>统计月份:{{ monthlyClosingMonthLabel }};金额单位为万元。</span>
|
||||
<span v-if="!monthlyClosingLoading && !monthlyClosingError">
|
||||
{{ monthlyClosingView === 'store'
|
||||
? '店铺期末余额 = 期初余额 + 收入 - 支出;缺少期初时显示“—”。'
|
||||
: '8 个平台沿用各自账单页的期末余额口径。' }}
|
||||
</span>
|
||||
</footer>
|
||||
</section>
|
||||
|
||||
<div
|
||||
v-if="detailOpen"
|
||||
class="flow-detail-modal"
|
||||
@ -410,7 +290,6 @@ export default {
|
||||
},
|
||||
data() {
|
||||
const range = currentMonthRange();
|
||||
const closingMonth = range.end.slice(0, 7);
|
||||
|
||||
return {
|
||||
dateRange: { ...range },
|
||||
@ -433,33 +312,6 @@ export default {
|
||||
detailStore: '',
|
||||
detailBillItem: '',
|
||||
bodyOverflowBeforeDetail: '',
|
||||
monthlyClosingLoading: true,
|
||||
monthlyClosingError: '',
|
||||
monthlyClosingRequestSerial: 0,
|
||||
monthlyClosingView: 'platform',
|
||||
monthlyClosingStoreLoading: false,
|
||||
monthlyClosingStoreError: '',
|
||||
monthlyClosingStoreLoaded: false,
|
||||
monthlyClosingStoreRequestSerial: 0,
|
||||
monthlyStoreMonthDetails: [],
|
||||
monthlyClosing: {
|
||||
month: closingMonth,
|
||||
period: {
|
||||
start: range.start,
|
||||
end: range.end,
|
||||
},
|
||||
months: [{
|
||||
key: closingMonth,
|
||||
label: `${Number(closingMonth.slice(5))}月`,
|
||||
}],
|
||||
summary: {
|
||||
platform_count: 8,
|
||||
available_platform_count: 0,
|
||||
complete: false,
|
||||
},
|
||||
total: null,
|
||||
platforms: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -674,99 +526,9 @@ export default {
|
||||
const tab = this.detailTabs.find(item => item.key === this.detailTab);
|
||||
return tab ? tab.label : '';
|
||||
},
|
||||
monthlyClosingRows() {
|
||||
return [
|
||||
...(this.monthlyClosing.total ? [this.monthlyClosing.total] : []),
|
||||
...(Array.isArray(this.monthlyClosing.platforms) ? this.monthlyClosing.platforms : []),
|
||||
];
|
||||
},
|
||||
monthlyClosingCountText() {
|
||||
if (this.monthlyClosingView === 'platform') {
|
||||
return `${this.monthlyClosing.summary.available_platform_count} / ${this.monthlyClosing.summary.platform_count} 个平台`;
|
||||
}
|
||||
if (this.monthlyClosingStoreLoading) return '正在读取店铺…';
|
||||
if (this.monthlyClosingStoreError) return '店铺数据暂不可用';
|
||||
|
||||
const total = this.monthlyStoreRows.filter(row => !row.isTotal).length;
|
||||
const incomplete = this.monthlyStoreRows.filter(row => !row.isTotal && !row.available).length;
|
||||
|
||||
return incomplete > 0
|
||||
? `${total} 个店铺 · ${incomplete} 个期初待补`
|
||||
: `${total} 个店铺`;
|
||||
},
|
||||
monthlyClosingMonth() {
|
||||
const selectedDate = this.queryRange.end || this.queryRange.start;
|
||||
|
||||
return selectedDate ? selectedDate.slice(0, 7) : '';
|
||||
},
|
||||
monthlyClosingMonthLabel() {
|
||||
const monthKey = this.monthlyClosingMonth;
|
||||
if (!/^\d{4}-\d{2}$/.test(monthKey)) return '';
|
||||
|
||||
return `${monthKey.slice(0, 4)}年${Number(monthKey.slice(5))}月`;
|
||||
},
|
||||
monthlyStoreRows() {
|
||||
const months = Array.isArray(this.monthlyClosing.months)
|
||||
? this.monthlyClosing.months.map(month => month.key)
|
||||
: [];
|
||||
const platformOrder = new Map(
|
||||
this.monthlyClosing.platforms.map((platform, index) => [platform.slug, index]),
|
||||
);
|
||||
const grouped = new Map();
|
||||
|
||||
this.monthlyStoreMonthDetails.forEach((result) => {
|
||||
(Array.isArray(result.details) ? result.details : []).forEach((detail) => {
|
||||
const key = [detail.platform_slug, detail.store_name].join('|');
|
||||
const row = grouped.get(key) || {
|
||||
key,
|
||||
platform_slug: detail.platform_slug,
|
||||
platform: detail.platform,
|
||||
store_name: detail.store_name,
|
||||
balances: Object.fromEntries(months.map(month => [month, null])),
|
||||
};
|
||||
row.balances[result.month] = detail.amount === null || detail.amount === undefined
|
||||
? null
|
||||
: (Number.isFinite(Number(detail.amount))
|
||||
? Math.round(Number(detail.amount) * 100) / 100
|
||||
: null);
|
||||
grouped.set(key, row);
|
||||
});
|
||||
});
|
||||
|
||||
const rows = Array.from(grouped.values()).map(row => ({
|
||||
...row,
|
||||
available: months.some(month => row.balances[month] !== null),
|
||||
}));
|
||||
const selectedMonth = months[0];
|
||||
rows.sort((left, right) => {
|
||||
const leftBalance = left.balances[selectedMonth];
|
||||
const rightBalance = right.balances[selectedMonth];
|
||||
|
||||
return (
|
||||
(leftBalance === null ? 1 : 0) - (rightBalance === null ? 1 : 0)
|
||||
|| (leftBalance === null ? 0 : rightBalance)
|
||||
- (leftBalance === null ? 0 : leftBalance)
|
||||
|| (platformOrder.get(left.platform_slug) ?? 99)
|
||||
- (platformOrder.get(right.platform_slug) ?? 99)
|
||||
|| left.store_name.localeCompare(right.store_name, 'zh-CN')
|
||||
);
|
||||
});
|
||||
|
||||
if (!this.monthlyClosing.total) return rows;
|
||||
|
||||
return [{
|
||||
...this.monthlyClosing.total,
|
||||
key: 'monthly-store-total',
|
||||
platform: '合计',
|
||||
store_name: `合计 · ${rows.length} 个店铺`,
|
||||
isTotal: true,
|
||||
available: true,
|
||||
}, ...rows];
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchOverview();
|
||||
this.fetchMonthlyClosing();
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.detailOpen) this.unlockDetailScroll();
|
||||
@ -805,96 +567,6 @@ export default {
|
||||
this.queryRange = { ...this.dateRange };
|
||||
if (this.detailOpen) this.closeDetails();
|
||||
this.fetchOverview();
|
||||
this.fetchMonthlyClosing();
|
||||
},
|
||||
async fetchMonthlyClosing() {
|
||||
const requestId = ++this.monthlyClosingRequestSerial;
|
||||
this.monthlyClosingLoading = true;
|
||||
this.monthlyClosingError = '';
|
||||
++this.monthlyClosingStoreRequestSerial;
|
||||
this.monthlyClosingStoreLoaded = false;
|
||||
this.monthlyStoreMonthDetails = [];
|
||||
|
||||
try {
|
||||
const response = await window.axios.get(`${this.apiBase}/monthly-closing`, {
|
||||
params: {
|
||||
month: this.monthlyClosingMonth,
|
||||
},
|
||||
});
|
||||
if (requestId !== this.monthlyClosingRequestSerial) return;
|
||||
const body = response.data || {};
|
||||
this.monthlyClosing = {
|
||||
month: body.month || this.monthlyClosingMonth,
|
||||
period: body.period || {
|
||||
start: this.queryRange.start,
|
||||
end: this.queryRange.end,
|
||||
},
|
||||
months: Array.isArray(body.months) ? body.months : [],
|
||||
summary: {
|
||||
platform_count: 8,
|
||||
available_platform_count: 0,
|
||||
complete: false,
|
||||
...(body.summary || {}),
|
||||
},
|
||||
total: body.total || null,
|
||||
platforms: Array.isArray(body.platforms) ? body.platforms : [],
|
||||
};
|
||||
if (this.monthlyClosingView === 'store') {
|
||||
this.fetchMonthlyClosingStores();
|
||||
}
|
||||
} catch (error) {
|
||||
if (requestId !== this.monthlyClosingRequestSerial) return;
|
||||
this.monthlyClosingError = (error.response && error.response.data && error.response.data.message)
|
||||
|| '月度期末余额汇总暂时无法读取,请稍后重试。';
|
||||
} finally {
|
||||
if (requestId === this.monthlyClosingRequestSerial) {
|
||||
this.monthlyClosingLoading = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
selectMonthlyClosingView(view) {
|
||||
if (!['platform', 'store'].includes(view) || this.monthlyClosingView === view) return;
|
||||
this.monthlyClosingView = view;
|
||||
if (view === 'store' && !this.monthlyClosingStoreLoaded && !this.monthlyClosingStoreLoading) {
|
||||
this.fetchMonthlyClosingStores();
|
||||
}
|
||||
},
|
||||
async fetchMonthlyClosingStores() {
|
||||
const month = Array.isArray(this.monthlyClosing.months)
|
||||
? this.monthlyClosing.months[0]
|
||||
: null;
|
||||
if (!month) return;
|
||||
|
||||
const requestId = ++this.monthlyClosingStoreRequestSerial;
|
||||
this.monthlyClosingStoreLoading = true;
|
||||
this.monthlyClosingStoreError = '';
|
||||
this.monthlyStoreMonthDetails = [];
|
||||
|
||||
try {
|
||||
const response = await window.axios.get(`${this.apiBase}/balance-store-detail`, {
|
||||
params: {
|
||||
month: month.key,
|
||||
},
|
||||
});
|
||||
if (requestId !== this.monthlyClosingStoreRequestSerial) return;
|
||||
|
||||
this.monthlyStoreMonthDetails = [{
|
||||
month: month.key,
|
||||
details: Array.isArray(response.data && response.data.details)
|
||||
? response.data.details
|
||||
: [],
|
||||
}];
|
||||
this.monthlyClosingStoreLoaded = true;
|
||||
} catch (error) {
|
||||
if (requestId !== this.monthlyClosingStoreRequestSerial) return;
|
||||
this.monthlyClosingStoreLoaded = false;
|
||||
this.monthlyClosingStoreError = (error.response && error.response.data && error.response.data.message)
|
||||
|| '店铺月度期末余额暂时无法读取,请稍后重试。';
|
||||
} finally {
|
||||
if (requestId === this.monthlyClosingStoreRequestSerial) {
|
||||
this.monthlyClosingStoreLoading = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
changeDate(field, value) {
|
||||
const date = value || '';
|
||||
@ -1044,15 +716,6 @@ export default {
|
||||
|
||||
return amount === '—' ? '' : `原值:${amount}`;
|
||||
},
|
||||
monthlyBalanceWan(row, month) {
|
||||
const value = row && row.balances ? row.balances[month] : null;
|
||||
if (value === null || value === undefined || !Number.isFinite(Number(value))) return '—';
|
||||
|
||||
return `¥${(Number(value) / 10000).toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
})}万`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@ -1404,218 +1067,6 @@ export default {
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
}
|
||||
.monthly-closing-card {
|
||||
margin-top: .75rem;
|
||||
overflow: hidden;
|
||||
border: 1px solid #ded6c8;
|
||||
border-radius: .65rem;
|
||||
background: rgba(255, 255, 255, .92);
|
||||
box-shadow: 0 10px 28px rgba(63, 50, 29, .05);
|
||||
}
|
||||
.monthly-closing-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: .85rem 1rem;
|
||||
border-bottom: 1px solid #eee7da;
|
||||
background: linear-gradient(90deg, #fbf5e9, rgba(255, 255, 255, .9));
|
||||
}
|
||||
.monthly-closing-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
}
|
||||
.monthly-closing-head h3 {
|
||||
margin: 0;
|
||||
color: var(--overview-ink);
|
||||
font-size: .9375rem;
|
||||
}
|
||||
.monthly-closing-head p {
|
||||
margin: .2rem 0 0;
|
||||
color: var(--overview-muted);
|
||||
font-size: .6875rem;
|
||||
}
|
||||
.monthly-closing-badge {
|
||||
padding: .1rem .4rem;
|
||||
border: 1px solid #c9b68f;
|
||||
border-radius: 999px;
|
||||
background: #fffaf0;
|
||||
color: #775725;
|
||||
font-size: .625rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.monthly-closing-count {
|
||||
flex: 0 0 auto;
|
||||
color: #55743f;
|
||||
font-size: .6875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.monthly-closing-head-actions {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
gap: .75rem;
|
||||
}
|
||||
.monthly-closing-view-switch {
|
||||
display: inline-flex;
|
||||
padding: 2px;
|
||||
border: 1px solid #d8cdbc;
|
||||
border-radius: .5rem;
|
||||
background: #f7f1e5;
|
||||
}
|
||||
.monthly-closing-view-switch button {
|
||||
height: 1.9rem;
|
||||
padding: 0 .75rem;
|
||||
border: 0;
|
||||
border-radius: .38rem;
|
||||
background: transparent;
|
||||
color: var(--overview-muted);
|
||||
font: 600 .6875rem/1.2 inherit;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
.monthly-closing-view-switch button:hover {
|
||||
color: var(--overview-gold);
|
||||
}
|
||||
.monthly-closing-view-switch button.is-active {
|
||||
background: #fffdf8;
|
||||
color: var(--overview-gold);
|
||||
box-shadow: 0 1px 2px rgba(63, 50, 29, .10);
|
||||
}
|
||||
.monthly-closing-view-switch button:focus-visible {
|
||||
box-shadow: 0 0 0 3px rgba(138, 104, 47, .18);
|
||||
outline: none;
|
||||
}
|
||||
.monthly-closing-scroll {
|
||||
overflow-x: auto;
|
||||
overscroll-behavior-inline: contain;
|
||||
}
|
||||
.monthly-closing-scroll.is-store {
|
||||
max-height: 30rem;
|
||||
overflow: auto;
|
||||
}
|
||||
.monthly-closing-scroll.is-store .monthly-closing-table thead th {
|
||||
position: sticky;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
}
|
||||
.monthly-closing-table {
|
||||
width: 100%;
|
||||
min-width: 34rem;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
font-size: .75rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.monthly-closing-store-table {
|
||||
min-width: 46rem;
|
||||
}
|
||||
.monthly-closing-table th,
|
||||
.monthly-closing-table td {
|
||||
padding: .65rem .8rem;
|
||||
border-right: 1px solid #eeeae2;
|
||||
border-bottom: 1px solid #eeeae2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.monthly-closing-table thead th {
|
||||
background: #f8f2e6;
|
||||
color: #5f574b;
|
||||
font-size: .6875rem;
|
||||
font-weight: 700;
|
||||
text-align: left;
|
||||
}
|
||||
.monthly-closing-table thead th.is-number,
|
||||
.monthly-closing-table td.is-number {
|
||||
text-align: right;
|
||||
}
|
||||
.monthly-closing-table thead th:last-child,
|
||||
.monthly-closing-table td:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
.monthly-closing-table thead th.monthly-closing-month-head {
|
||||
background: #f3e5c9;
|
||||
color: #6e5025;
|
||||
}
|
||||
.monthly-closing-table tbody th {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
background: #fff;
|
||||
color: #3e3a34;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
}
|
||||
.monthly-closing-table tbody tr:hover th,
|
||||
.monthly-closing-table tbody tr:hover td {
|
||||
background: #fbf7ef;
|
||||
}
|
||||
.monthly-closing-table tbody tr.is-total th,
|
||||
.monthly-closing-table tbody tr.is-total td {
|
||||
background: #f8f2e6;
|
||||
color: #171717;
|
||||
font-weight: 700;
|
||||
}
|
||||
.monthly-closing-table tbody tr.is-unavailable td {
|
||||
color: #937b57;
|
||||
}
|
||||
.monthly-closing-store-table .monthly-store-name {
|
||||
min-width: 15rem;
|
||||
}
|
||||
.monthly-closing-foot {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: .35rem 1rem;
|
||||
padding: .55rem .8rem;
|
||||
border-top: 1px solid #eee7da;
|
||||
background: #fcfaf5;
|
||||
color: var(--overview-muted);
|
||||
font-size: .625rem;
|
||||
}
|
||||
.monthly-closing-loading,
|
||||
.monthly-closing-error {
|
||||
min-height: 6rem;
|
||||
}
|
||||
.monthly-closing-loading,
|
||||
.monthly-closing-error {
|
||||
padding: 1rem;
|
||||
}
|
||||
.monthly-closing-loading {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: .5rem;
|
||||
}
|
||||
.monthly-closing-loading span {
|
||||
height: .75rem;
|
||||
border-radius: .25rem;
|
||||
background: linear-gradient(90deg, #eee8dd 25%, #f8f4ed 50%, #eee8dd 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: overview-shimmer 1.25s infinite linear;
|
||||
}
|
||||
.monthly-closing-loading p {
|
||||
grid-column: 1 / -1;
|
||||
margin: 0;
|
||||
color: var(--overview-muted);
|
||||
font-size: .6875rem;
|
||||
}
|
||||
.monthly-closing-error {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: .75rem;
|
||||
color: #8a3a27;
|
||||
font-size: .75rem;
|
||||
}
|
||||
.monthly-closing-error button {
|
||||
padding: .25rem .55rem;
|
||||
border: 1px solid currentColor;
|
||||
border-radius: .35rem;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
.flow-detail-modal {
|
||||
position: fixed;
|
||||
z-index: 10000;
|
||||
@ -1907,19 +1358,6 @@ export default {
|
||||
.overview-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.monthly-closing-head {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
.monthly-closing-head-actions {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.monthly-closing-foot {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
.flow-detail-modal {
|
||||
align-items: stretch;
|
||||
padding: .5rem;
|
||||
|
||||
@ -39,8 +39,6 @@ Route::post('store-account-passwords/{account}/reveal', 'Business\StoreAccountPa
|
||||
|
||||
Route::get('ecommerce-finance/account-bills/overview', [StoreReceivingBalanceOverview::class, 'index'])
|
||||
->name('ecommerce-finance.account-bills.overview');
|
||||
Route::get('ecommerce-finance/account-bills/overview/monthly-closing', [StoreReceivingBalanceOverview::class, 'monthlyClosing'])
|
||||
->name('ecommerce-finance.account-bills.overview.monthly-closing');
|
||||
Route::get('ecommerce-finance/account-bills/overview/income-detail', [StoreReceivingBalanceOverview::class, 'incomeDetail'])
|
||||
->name('ecommerce-finance.account-bills.overview.income-detail');
|
||||
Route::get('ecommerce-finance/account-bills/overview/expense-detail', [StoreReceivingBalanceOverview::class, 'expenseDetail'])
|
||||
|
||||
@ -79,42 +79,6 @@ class StoreReceivingBalanceOverviewEndpointTest extends FeatureTestCase
|
||||
->assertJsonPath('summary.complete', true);
|
||||
}
|
||||
|
||||
public function test_monthly_closing_endpoint_returns_selected_month_and_eight_platforms(): void
|
||||
{
|
||||
$this->user->attachPermission(
|
||||
Permission::where('name', 'read-admin-panel')->firstOrFail()
|
||||
);
|
||||
|
||||
$payload = [
|
||||
'month' => '2026-10',
|
||||
'months' => [['key' => '2026-10', 'label' => '10月']],
|
||||
'summary' => [
|
||||
'platform_count' => 8,
|
||||
'available_platform_count' => 8,
|
||||
'complete' => true,
|
||||
],
|
||||
'total' => [
|
||||
'slug' => 'total',
|
||||
'name' => '合计',
|
||||
'balances' => ['2026-10' => 1040.0],
|
||||
],
|
||||
'platforms' => [],
|
||||
];
|
||||
$service = Mockery::mock(StoreReceivingBalanceOverviewService::class);
|
||||
$service->shouldReceive('monthlyClosing')->once()->with('2026-10')->andReturn($payload);
|
||||
$this->app->instance(StoreReceivingBalanceOverviewService::class, $service);
|
||||
|
||||
$this->loginAs()
|
||||
->getJson(route('ecommerce-finance.account-bills.overview.monthly-closing', [
|
||||
'month' => '2026-10',
|
||||
]))
|
||||
->assertOk()
|
||||
->assertJsonCount(1, 'months')
|
||||
->assertJsonPath('months.0.label', '10月')
|
||||
->assertJsonPath('summary.complete', true)
|
||||
->assertJsonMissingPath('total.change');
|
||||
}
|
||||
|
||||
public function test_overview_page_uses_the_vue_mount_point(): void
|
||||
{
|
||||
$this->user->attachPermission(
|
||||
|
||||
@ -320,40 +320,6 @@ class StoreReceivingBalanceOverviewServiceTest extends TestCase
|
||||
$this->assertNull($rows[0]['amount']);
|
||||
}
|
||||
|
||||
public function test_monthly_closing_returns_only_the_selected_month_for_all_platforms(): void
|
||||
{
|
||||
$seenPeriods = [];
|
||||
$service = $this->service(function (string $platform, string $start, string $end) use (&$seenPeriods): array {
|
||||
$seenPeriods[] = [$platform, $start, $end];
|
||||
$index = array_search($platform, array_column(StoreReceivingBalanceOverviewService::PLATFORMS, 'slug'), true) + 1;
|
||||
|
||||
return [
|
||||
'opening_balance' => 1000.00 * $index,
|
||||
'income' => 10.00,
|
||||
'expense' => 0.0,
|
||||
];
|
||||
});
|
||||
|
||||
$result = $service->monthlyClosing('2026-10');
|
||||
|
||||
$this->assertCount(8, $seenPeriods);
|
||||
$this->assertSame(
|
||||
['2026-10-01|2026-10-31'],
|
||||
array_values(array_unique(array_map(
|
||||
static fn (array $period): string => $period[1] . '|' . $period[2],
|
||||
$seenPeriods,
|
||||
))),
|
||||
);
|
||||
$this->assertSame(['2026-10'], array_column($result['months'], 'key'));
|
||||
$this->assertSame('10月', $result['months'][0]['label']);
|
||||
$this->assertCount(8, $result['platforms']);
|
||||
$this->assertSame(['2026-10' => 1010.00], $result['platforms'][0]['balances']);
|
||||
$this->assertArrayNotHasKey('change', $result['platforms'][0]);
|
||||
$this->assertSame('合计', $result['total']['name']);
|
||||
$this->assertTrue($result['summary']['complete']);
|
||||
$this->assertSame(8, $result['summary']['available_platform_count']);
|
||||
}
|
||||
|
||||
private function service(callable $summary): StoreReceivingBalanceOverviewService
|
||||
{
|
||||
return new class($summary) extends StoreReceivingBalanceOverviewService {
|
||||
|
||||
@ -65,35 +65,16 @@ class StoreReceivingBalanceOverviewTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function test_overview_contains_selected_month_closing_summary_without_comparison(): void
|
||||
public function test_overview_does_not_render_monthly_closing_summary(): void
|
||||
{
|
||||
$source = file_get_contents(
|
||||
__DIR__ . '/../../../resources/assets/js/components/business/StoreReceivingBalanceOverview.vue'
|
||||
);
|
||||
|
||||
$this->assertStringContainsString('月度期末余额汇总', $source);
|
||||
$this->assertStringContainsString('平台账户', $source);
|
||||
$this->assertStringContainsString('monthly-closing-table', $source);
|
||||
$this->assertStringContainsString('monthlyClosingMonthLabel', $source);
|
||||
$this->assertStringContainsString('const selectedDate = this.queryRange.end || this.queryRange.start', $source);
|
||||
$this->assertStringContainsString('month: this.monthlyClosingMonth', $source);
|
||||
$this->assertStringContainsString('month: month.key', $source);
|
||||
$this->assertStringContainsString("'各平台'", $source);
|
||||
$this->assertStringContainsString('金额单位:万元', $source);
|
||||
$this->assertStringContainsString('monthlyBalanceWan', $source);
|
||||
$this->assertStringContainsString('Number(value) / 10000', $source);
|
||||
$this->assertStringContainsString('monthlyClosingRows', $source);
|
||||
$this->assertStringContainsString('fetchMonthlyClosing', $source);
|
||||
$this->assertStringContainsString('店铺汇总', $source);
|
||||
$this->assertStringContainsString('monthly-closing-view-switch', $source);
|
||||
$this->assertStringContainsString('selectMonthlyClosingView', $source);
|
||||
$this->assertStringContainsString('fetchMonthlyClosingStores', $source);
|
||||
$this->assertStringContainsString('monthlyStoreRows', $source);
|
||||
$this->assertStringContainsString('monthly-closing-store-table', $source);
|
||||
$this->assertStringContainsString('店铺期末余额 = 期初余额 + 收入 - 支出', $source);
|
||||
$this->assertStringContainsString('缺少期初时显示“—”', $source);
|
||||
$this->assertStringNotContainsString('月度期末余额汇总', $source);
|
||||
$this->assertStringNotContainsString('monthly-closing', $source);
|
||||
$this->assertStringNotContainsString('fetchMonthlyClosing', $source);
|
||||
$this->assertStringNotContainsString('monthlyClosing', $source);
|
||||
$this->assertStringNotContainsString('环比', $source);
|
||||
$this->assertStringNotContainsString('2026 年 7-10 月', $source);
|
||||
$this->assertStringNotContainsString('body.comparison', $source);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user