feat(selfop): show customer names and filter sales and turnover

This commit is contained in:
2026-10-14 16:15:40 +08:00
parent 2a9c23ebc6
commit 46364d3f89
17 changed files with 125 additions and 28 deletions
@@ -23,8 +23,10 @@ import { totalText, turnoverTotalNote, inventoryCollectionNotice, turnoverChart,
import AnalyticsChart from './AnalyticsChart.vue'
import ChartDetailsDialog from './ChartDetailsDialog.vue'
import SkuTrend from './SkuTrend.vue'
import { useOwnerDirectory } from '../useOwnerDirectory'
const route = useRoute(), router = useRouter(), access = useAccessPreviewStore()
const ownerDirectory = useOwnerDirectory()
const emit = defineEmits<{ warehouseSelected: [code: string] }>()
class TurnoverContractError extends Error {}
const permitted = computed(() => access.can('platform:self-operated-analytics:list'))
@@ -45,6 +47,7 @@ const fields = computed<QueryField[]>(() => [
{ key: 'rangePreset', label: '统计时间范围', type: 'select', width: 180, props: { clearable: false }, options: overviewRangeOptions },
...(filters.value.rangePreset === 'CUSTOM' ? [{ key: 'customRange', label: '日期范围', type: 'daterange' as const, width: 300, props: { disabledDate: (date: Date) => date.toLocaleDateString('en-CA') > yesterdayBeijing() } }] : []),
{ key: 'groupBy', label: '分组', type: 'select', width: 120, options: [{ value: 'SKU', label: '按SKU' }, { value: 'OWNER', label: '按货主' }], props: { clearable: false } },
{ key: 'customerCode', label: '客户', type: 'select', width: 210, options: ownerDirectory.options.value, placeholder: '名称或编码搜索', props: { filterable: true, clearable: true } },
...(filters.value.groupBy === 'SKU' ? [{ key: 'sku', label: 'SKU', type: 'input' as const, width: 175, placeholder: 'SKU精确查询' }] : []),
{ key: 'focus', label: '关注条件', type: 'select', width: 165, props: { clearable: false }, options: [{ value: 'ALL', label: '全部' }, { value: 'NO_OUTBOUND_IN_WINDOW', label: '所选时段无出库' }, { value: 'COVERAGE_GT', label: '消化较慢' }, { value: 'COVERAGE_LE', label: '消化较快' }, { value: 'FROZEN', label: '有冻结库存' }, { value: 'DEFECTIVE', label: '有次品库存' }, { value: 'UNASSESSABLE', label: '待评估' }] },
{ key: 'turnoverStatus', label: '周转样本', type: 'select', width: 160, options: [{ value: '', label: '全部样本状态' }, { value: 'INSUFFICIENT', label: '周转样本不足' }] },
@@ -138,6 +141,7 @@ async function load(refresh = false) {
if (q.includeOverview && (!result.summary || !result.charts)) throw new TurnoverContractError('本次概览未完整返回,请整体刷新')
data.value = { ...result, summary: q.includeOverview ? result.summary : previous?.summary ?? null, charts: q.includeOverview ? result.charts : previous?.charts ?? null }
applied.value = q; error.value = null; emit('warehouseSelected', q.warehouseCode)
void ownerDirectory.load(result.context.analyticsContext, q.warehouseCode, request.signal)
try {
if (q.coverageDaysThreshold != null) sessionStorage.setItem(`selfop-turnover-threshold:${q.warehouseCode}`, String(q.coverageDaysThreshold))
} catch { /* optional preference */ }
@@ -155,7 +159,7 @@ function changeFilters(next: QueryModel) {
if (next.groupBy !== filters.value.groupBy) {
next = { ...next, sku: '', sortBy: 'PHYSICAL_DESC' }; notice.value = '已切换分组,SKU条件及专属排序已清除'
}
const changed = ['warehouseCode', 'rangePreset', 'groupBy'].some(k => next[k] !== filters.value[k])
const changed = ['warehouseCode', 'rangePreset', 'groupBy', 'customerCode'].some(k => next[k] !== filters.value[k])
if (next.warehouseCode !== filters.value.warehouseCode) next = { ...next, coverageDaysThreshold: restoreThreshold(String(next.warehouseCode || '')) }
filters.value = next
if (changed) { page.value = 1; void load() }
@@ -229,6 +233,7 @@ onUnmounted(() => { fence.cancel(); runtimeFence.cancel(); trendFence.cancel() }
<template #toolbar-start><slot name="view-tabs"><h2 class="turnover-title">库存周转<span>销量 · 库存 · 周转效率</span></h2></slot></template>
<template #toolbar-actions><el-button @click="router.push('/self-operated/collection')">数据采集</el-button><el-button @click="page = 1; load(true)">整体刷新</el-button><el-button @click="expanded = !expanded">{{ expanded ? '收起扩展列' : '扩展列' }}</el-button><el-popover title="统计说明" trigger="click" placement="bottom-end" :width="460" teleported><template #reference><el-button aria-label="库存周转统计说明" title="统计说明"><Info :size="16" /></el-button></template><div class="turnover-rules"><p>7/15/30天均为截至所选日期的完整滚动窗口。日均仅使用完整窗口;预计可售天数使用查询时固定的当前正品可售库存,不是库龄。</p><p>期间件数周转次数=同期完整出库÷每日首次合格计划库存的实物均值。缺日、缺商品或单位异常不补零;当前库存不代替历史平均库存。件数不是占用空间,快慢标签仅供人工复核。</p><p>货主视图按“含此类SKU的货主”筛选;未知数量保留已知小计,不平均SKU可售天数。库存过期不抹去有完整证据的历史销量与周转。</p></div></el-popover></template>
<template #filters>
<p v-if="ownerDirectory.error.value" role="alert">{{ ownerDirectory.error.value }}</p>
<QueryToolbar :model-value="filters" :fields="fields" ariaLabel="库存周转查询" preference-key="selfop-turnover-query" @update:model-value="changeFilters" @search="search" @reset="reset" />
</template>
<template #before-results>